@hubspot/cli 3.0.13-beta.0 → 3.0.13-beta.3
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/bin/cli.js +40 -2
- package/commands/accounts/use.js +82 -0
- package/commands/accounts.js +2 -0
- package/commands/config/set/allowUsageTracking.js +12 -19
- package/commands/config/set/defaultMode.js +11 -35
- package/commands/config/set/httpTimeout.js +27 -24
- package/commands/config/set.js +81 -13
- package/commands/logs.js +1 -7
- package/commands/module/marketplace-validate.js +77 -0
- package/commands/module.js +17 -0
- package/commands/project/logs.js +136 -61
- package/commands/project/open.js +75 -0
- package/commands/project.js +2 -0
- package/commands/sandbox/create.js +2 -2
- package/commands/sandbox.js +3 -1
- package/commands/theme/marketplace-validate.js +4 -2
- package/commands/upload.js +21 -10
- package/lib/hasFlag.js +15 -0
- package/lib/projects.js +11 -9
- package/lib/prompts/projectNamePrompt.js +29 -0
- package/lib/prompts/projectsLogsPrompt.js +108 -0
- package/lib/serverlessLogs.js +31 -11
- package/lib/supportHyperlinks.js +75 -0
- package/lib/supportsColor.js +129 -0
- package/lib/ui.js +22 -5
- package/lib/validators/__tests__/{BaseValidator.js → AbsoluteValidator.js} +3 -3
- package/lib/validators/__tests__/ModuleDependencyValidator.js +79 -0
- package/lib/validators/__tests__/ModuleValidator.js +22 -55
- package/lib/validators/__tests__/RelativeValidator.js +28 -0
- package/lib/validators/__tests__/TemplateValidator.js +1 -1
- package/lib/validators/__tests__/ThemeConfigValidator.js +1 -1
- package/lib/validators/__tests__/{DependencyValidator.js → ThemeDependencyValidator.js} +14 -12
- package/lib/validators/__tests__/ThemeModuleValidator.js +84 -0
- package/lib/validators/__tests__/validatorTestUtils.js +2 -0
- package/lib/validators/applyValidators.js +20 -4
- package/lib/validators/constants.js +4 -2
- package/lib/validators/index.js +8 -4
- package/lib/validators/marketplaceValidators/{BaseValidator.js → AbsoluteValidator.js} +8 -8
- package/lib/validators/marketplaceValidators/RelativeValidator.js +46 -0
- package/lib/validators/marketplaceValidators/module/ModuleDependencyValidator.js +101 -0
- package/lib/validators/marketplaceValidators/module/ModuleValidator.js +102 -0
- package/lib/validators/marketplaceValidators/theme/SectionValidator.js +2 -2
- package/lib/validators/marketplaceValidators/theme/TemplateValidator.js +2 -2
- package/lib/validators/marketplaceValidators/theme/ThemeConfigValidator.js +3 -3
- package/lib/validators/marketplaceValidators/theme/{DependencyValidator.js → ThemeDependencyValidator.js} +15 -12
- package/lib/validators/marketplaceValidators/theme/{ModuleValidator.js → ThemeModuleValidator.js} +5 -5
- package/package.json +4 -6
- package/commands/config/set/defaultAccount.js +0 -94
- package/commands/server.js +0 -80
- package/lib/server/updateContext.js +0 -105
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
2
|
+
const { fetchProject } = require('@hubspot/cli-lib/api/dfs');
|
|
3
|
+
const { promptUser } = require('./promptUtils');
|
|
4
|
+
const { getProjectConfig, ensureProjectExists } = require('../projects');
|
|
5
|
+
const { logger } = require('@hubspot/cli-lib/logger');
|
|
6
|
+
const { EXIT_CODES } = require('../enums/exitCodes');
|
|
7
|
+
|
|
8
|
+
const i18nKey = 'cli.lib.prompts.projectLogsPrompt';
|
|
9
|
+
|
|
10
|
+
const SERVERLESS_FUNCTION_TYPES = {
|
|
11
|
+
APP_FUNCTION: 'app-function',
|
|
12
|
+
PUBLIC_ENDPOINT: 'public-endpoint',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const projectLogsPrompt = (accountId, promptOptions = {}) => {
|
|
16
|
+
return promptUser([
|
|
17
|
+
{
|
|
18
|
+
name: 'projectName',
|
|
19
|
+
message: i18n(`${i18nKey}.projectName.message`),
|
|
20
|
+
when: !promptOptions.project,
|
|
21
|
+
default: async () => {
|
|
22
|
+
const { projectConfig } = await getProjectConfig();
|
|
23
|
+
return projectConfig && projectConfig.name ? projectConfig.name : null;
|
|
24
|
+
},
|
|
25
|
+
validate: name =>
|
|
26
|
+
!name.length ? i18n(`${i18nKey}.projectName.error`) : true,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'logType',
|
|
30
|
+
type: 'list',
|
|
31
|
+
message: i18n(`${i18nKey}.logType.message`),
|
|
32
|
+
when:
|
|
33
|
+
!promptOptions.app &&
|
|
34
|
+
!promptOptions.function &&
|
|
35
|
+
!promptOptions.endpoint,
|
|
36
|
+
choices: [
|
|
37
|
+
{
|
|
38
|
+
name: i18n(`${i18nKey}.logType.function`),
|
|
39
|
+
value: SERVERLESS_FUNCTION_TYPES.APP_FUNCTION,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: i18n(`${i18nKey}.logType.endpoint`),
|
|
43
|
+
value: SERVERLESS_FUNCTION_TYPES.PUBLIC_ENDPOINT,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'appName',
|
|
49
|
+
type: 'list',
|
|
50
|
+
message: i18n(`${i18nKey}.appName`),
|
|
51
|
+
when: ({ logType }) => {
|
|
52
|
+
return (
|
|
53
|
+
(promptOptions.function ||
|
|
54
|
+
logType === SERVERLESS_FUNCTION_TYPES.APP_FUNCTION) &&
|
|
55
|
+
!promptOptions.app &&
|
|
56
|
+
!promptOptions.endpoint
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
choices: async ({ projectName }) => {
|
|
60
|
+
const name = projectName || promptOptions.project;
|
|
61
|
+
|
|
62
|
+
await ensureProjectExists(accountId, name, {
|
|
63
|
+
allowCreate: false,
|
|
64
|
+
});
|
|
65
|
+
const { deployedBuild } = await fetchProject(accountId, name);
|
|
66
|
+
|
|
67
|
+
if (deployedBuild && deployedBuild.subbuildStatuses) {
|
|
68
|
+
return deployedBuild.subbuildStatuses
|
|
69
|
+
.filter(subbuild => subbuild.buildType === 'APP')
|
|
70
|
+
.map(subbuild => ({
|
|
71
|
+
name: subbuild.buildName,
|
|
72
|
+
value: subbuild.buildName,
|
|
73
|
+
}));
|
|
74
|
+
} else {
|
|
75
|
+
logger.debug('Failed to fetch project');
|
|
76
|
+
process.exit(EXIT_CODES.ERROR);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'functionName',
|
|
82
|
+
message: i18n(`${i18nKey}.functionName`),
|
|
83
|
+
when: ({ logType }) => {
|
|
84
|
+
return (
|
|
85
|
+
(promptOptions.app ||
|
|
86
|
+
logType === SERVERLESS_FUNCTION_TYPES.APP_FUNCTION) &&
|
|
87
|
+
!promptOptions.function &&
|
|
88
|
+
!promptOptions.endpoint
|
|
89
|
+
);
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'endpointName',
|
|
94
|
+
message: i18n(`${i18nKey}.endpointName`),
|
|
95
|
+
when: ({ logType }) => {
|
|
96
|
+
return (
|
|
97
|
+
logType === SERVERLESS_FUNCTION_TYPES.PUBLIC_ENDPOINT &&
|
|
98
|
+
!promptOptions.function &&
|
|
99
|
+
!promptOptions.endpoint
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
projectLogsPrompt,
|
|
108
|
+
};
|
package/lib/serverlessLogs.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const https = require('https');
|
|
2
|
-
|
|
2
|
+
const Spinnies = require('spinnies');
|
|
3
|
+
const chalk = require('chalk');
|
|
3
4
|
const { logger } = require('@hubspot/cli-lib/logger');
|
|
4
5
|
const { outputLogs } = require('@hubspot/cli-lib/lib/logs');
|
|
5
6
|
const {
|
|
@@ -8,18 +9,33 @@ const {
|
|
|
8
9
|
ApiErrorContext,
|
|
9
10
|
} = require('@hubspot/cli-lib/errorHandlers');
|
|
10
11
|
const { base64EncodeString } = require('@hubspot/cli-lib/lib/encoding');
|
|
11
|
-
const { handleKeypress } = require('@hubspot/cli-lib/lib/process');
|
|
12
|
+
const { handleExit, handleKeypress } = require('@hubspot/cli-lib/lib/process');
|
|
12
13
|
|
|
13
14
|
const { EXIT_CODES } = require('../lib/enums/exitCodes');
|
|
14
15
|
|
|
15
16
|
const TAIL_DELAY = 5000;
|
|
16
17
|
|
|
18
|
+
const handleUserInput = spinnies => {
|
|
19
|
+
const onTerminate = async () => {
|
|
20
|
+
spinnies.remove('tailLogs');
|
|
21
|
+
spinnies.remove('stopMessage');
|
|
22
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
handleExit(onTerminate);
|
|
26
|
+
handleKeypress(key => {
|
|
27
|
+
if ((key.ctrl && key.name == 'c') || key.name === 'q') {
|
|
28
|
+
onTerminate();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
17
33
|
const tailLogs = async ({
|
|
18
34
|
accountId,
|
|
19
35
|
compact,
|
|
20
|
-
spinnies,
|
|
21
36
|
fetchLatest,
|
|
22
37
|
tailCall,
|
|
38
|
+
name,
|
|
23
39
|
}) => {
|
|
24
40
|
let initialAfter;
|
|
25
41
|
|
|
@@ -44,7 +60,6 @@ const tailLogs = async ({
|
|
|
44
60
|
latestLog = await tailCall(after);
|
|
45
61
|
nextAfter = latestLog.paging.next.after;
|
|
46
62
|
} catch (e) {
|
|
47
|
-
spinnies.fail('tailLogs', { text: 'Stopped polling due to error.' });
|
|
48
63
|
if (e.statusCode !== 404) {
|
|
49
64
|
logApiErrorInstance(
|
|
50
65
|
e,
|
|
@@ -62,18 +77,23 @@ const tailLogs = async ({
|
|
|
62
77
|
});
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
tail(nextAfter);
|
|
80
|
+
setTimeout(async () => {
|
|
81
|
+
await tail(nextAfter);
|
|
67
82
|
}, TAIL_DELAY);
|
|
68
83
|
};
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
const spinnies = new Spinnies();
|
|
86
|
+
|
|
87
|
+
spinnies.add('tailLogs', {
|
|
88
|
+
text: `Following logs for ${name}`,
|
|
89
|
+
});
|
|
90
|
+
spinnies.add('stopMessage', {
|
|
91
|
+
text: `> Press ${chalk.bold('q')} to stop following`,
|
|
92
|
+
status: 'non-spinnable',
|
|
75
93
|
});
|
|
76
94
|
|
|
95
|
+
handleUserInput(spinnies);
|
|
96
|
+
|
|
77
97
|
await tail(initialAfter);
|
|
78
98
|
};
|
|
79
99
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const hasFlag = require('./hasFlag');
|
|
3
|
+
|
|
4
|
+
//See https://github.com/jamestalmage/supports-hyperlinks (License: https://github.com/jamestalmage/supports-hyperlinks/blob/master/license)
|
|
5
|
+
|
|
6
|
+
function parseVersion(versionString) {
|
|
7
|
+
if (/^\d{3,4}$/.test(versionString)) {
|
|
8
|
+
// Env var doesn't always use dots. example: 4601 => 46.1.0
|
|
9
|
+
const m = /(\d{1,2})(\d{2})/.exec(versionString);
|
|
10
|
+
return {
|
|
11
|
+
major: 0,
|
|
12
|
+
minor: parseInt(m[1], 10),
|
|
13
|
+
patch: parseInt(m[2], 10),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const versions = (versionString || '').split('.').map(n => parseInt(n, 10));
|
|
18
|
+
return {
|
|
19
|
+
major: versions[0],
|
|
20
|
+
minor: versions[1],
|
|
21
|
+
patch: versions[2],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function supportsHyperlink(stream) {
|
|
26
|
+
const { env } = process;
|
|
27
|
+
|
|
28
|
+
if (hasFlag('noHyperlinks')) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (stream && !stream.isTTY) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.platform === 'win32') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ('CI' in env) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ('TERM_PROGRAM' in env) {
|
|
45
|
+
const version = parseVersion(env.TERM_PROGRAM_VERSION);
|
|
46
|
+
|
|
47
|
+
switch (env.TERM_PROGRAM) {
|
|
48
|
+
case 'iTerm.app':
|
|
49
|
+
if (version.major === 3) {
|
|
50
|
+
return version.minor >= 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return version.major > 3;
|
|
54
|
+
// No default
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if ('VTE_VERSION' in env) {
|
|
59
|
+
// 0.50.0 was supposed to support hyperlinks, but throws a segfault
|
|
60
|
+
if (env.VTE_VERSION === '0.50.0') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const version = parseVersion(env.VTE_VERSION);
|
|
65
|
+
return version.major > 0 || version.minor >= 50;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
supportsHyperlink,
|
|
73
|
+
stdout: supportsHyperlink(process.stdout),
|
|
74
|
+
stderr: supportsHyperlink(process.stderr),
|
|
75
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const process = require('process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const tty = require('tty');
|
|
4
|
+
const hasFlag = require('./hasFlag');
|
|
5
|
+
|
|
6
|
+
const { env } = process;
|
|
7
|
+
|
|
8
|
+
//From: https://github.com/chalk/supports-color/blob/main/index.js (License: https://github.com/chalk/supports-color/blob/main/license)
|
|
9
|
+
|
|
10
|
+
function translateLevel(level) {
|
|
11
|
+
if (level === 0) {
|
|
12
|
+
return {
|
|
13
|
+
level,
|
|
14
|
+
hasBasic: false,
|
|
15
|
+
has256: false,
|
|
16
|
+
has16m: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
level,
|
|
22
|
+
hasBasic: true,
|
|
23
|
+
has256: level >= 2,
|
|
24
|
+
has16m: level >= 3,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _supportsColor(haveStream, { streamIsTTY } = {}) {
|
|
29
|
+
if (haveStream && !streamIsTTY) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const min = 0;
|
|
34
|
+
|
|
35
|
+
if (env.TERM === 'dumb') {
|
|
36
|
+
return min;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (hasFlag('noColor')) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (process.platform === 'win32') {
|
|
44
|
+
// Windows 10 build 10586 is the first Windows release that supports 256 colors.
|
|
45
|
+
// Windows 10 build 14931 is the first release that supports 16m/TrueColor.
|
|
46
|
+
const osRelease = os.release().split('.');
|
|
47
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
48
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ('CI' in env) {
|
|
55
|
+
if (
|
|
56
|
+
[
|
|
57
|
+
'TRAVIS',
|
|
58
|
+
'CIRCLECI',
|
|
59
|
+
'APPVEYOR',
|
|
60
|
+
'GITLAB_CI',
|
|
61
|
+
'GITHUB_ACTIONS',
|
|
62
|
+
'BUILDKITE',
|
|
63
|
+
'DRONE',
|
|
64
|
+
].some(sign => sign in env) ||
|
|
65
|
+
env.CI_NAME === 'codeship'
|
|
66
|
+
) {
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return min;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check for Azure DevOps pipelines
|
|
74
|
+
if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (env.COLORTERM === 'truecolor') {
|
|
79
|
+
return 3;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ('TERM_PROGRAM' in env) {
|
|
83
|
+
const version = Number.parseInt(
|
|
84
|
+
(env.TERM_PROGRAM_VERSION || '').split('.')[0],
|
|
85
|
+
10
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
switch (env.TERM_PROGRAM) {
|
|
89
|
+
case 'iTerm.app':
|
|
90
|
+
return version >= 3 ? 3 : 2;
|
|
91
|
+
case 'Apple_Terminal':
|
|
92
|
+
return 2;
|
|
93
|
+
// No default
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
98
|
+
return 2;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)
|
|
103
|
+
) {
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ('COLORTERM' in env) {
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return min;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function createSupportsColor(stream, options = {}) {
|
|
115
|
+
const level = _supportsColor(stream, {
|
|
116
|
+
streamIsTTY: stream && stream.isTTY,
|
|
117
|
+
...options,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return translateLevel(level);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const supportsColor = {
|
|
124
|
+
createSupportsColor,
|
|
125
|
+
stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
|
|
126
|
+
stderr: createSupportsColor({ isTTY: tty.isatty(2) }),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
module.exports = supportsColor;
|
package/lib/ui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const supportsHyperlinks = require('
|
|
2
|
+
const supportsHyperlinks = require('../lib/supportHyperlinks');
|
|
3
|
+
const supportsColor = require('../lib/supportsColor');
|
|
3
4
|
const { getAccountConfig } = require('@hubspot/cli-lib/lib/config');
|
|
4
5
|
const { logger } = require('@hubspot/cli-lib/logger');
|
|
5
6
|
|
|
@@ -12,6 +13,19 @@ const uiLine = () => {
|
|
|
12
13
|
logger.log('-'.repeat(50));
|
|
13
14
|
};
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Returns an object that aggregates what the terminal supports (eg. hyperlinks and color)
|
|
18
|
+
*
|
|
19
|
+
* @returns {object}
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const getTerminalUISupport = () => {
|
|
23
|
+
return {
|
|
24
|
+
hyperlinks: supportsHyperlinks.stdout,
|
|
25
|
+
color: supportsColor.stdout.hasBasic,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
15
29
|
/**
|
|
16
30
|
* Returns a hyperlink or link and description
|
|
17
31
|
*
|
|
@@ -20,8 +34,9 @@ const uiLine = () => {
|
|
|
20
34
|
* @param {object} options
|
|
21
35
|
* @returns {string}
|
|
22
36
|
*/
|
|
23
|
-
const uiLink = (linkText, url
|
|
24
|
-
|
|
37
|
+
const uiLink = (linkText, url) => {
|
|
38
|
+
const terminalUISupport = getTerminalUISupport();
|
|
39
|
+
if (terminalUISupport.hyperlinks) {
|
|
25
40
|
const result = [
|
|
26
41
|
'\u001B]8;;',
|
|
27
42
|
url,
|
|
@@ -29,9 +44,11 @@ const uiLink = (linkText, url, options = {}) => {
|
|
|
29
44
|
linkText,
|
|
30
45
|
'\u001B]8;;\u0007',
|
|
31
46
|
].join('');
|
|
32
|
-
return
|
|
47
|
+
return terminalUISupport.color ? chalk.cyan(result) : result;
|
|
33
48
|
} else {
|
|
34
|
-
return
|
|
49
|
+
return terminalUISupport.color
|
|
50
|
+
? `${linkText}: ${chalk.cyan(url)}`
|
|
51
|
+
: `${linkText}: ${url}`;
|
|
35
52
|
}
|
|
36
53
|
};
|
|
37
54
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const
|
|
1
|
+
const AbsoluteValidator = require('../marketplaceValidators/AbsoluteValidator');
|
|
2
2
|
const { VALIDATION_RESULT } = require('../constants');
|
|
3
3
|
|
|
4
|
-
const Validator = new
|
|
4
|
+
const Validator = new AbsoluteValidator({
|
|
5
5
|
name: 'Test validator',
|
|
6
6
|
key: 'validatorKey',
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
describe('validators/marketplaceValidators/
|
|
9
|
+
describe('validators/marketplaceValidators/AbsoluteValidator', () => {
|
|
10
10
|
it('getSuccess returns expected object', async () => {
|
|
11
11
|
const success = Validator.getSuccess();
|
|
12
12
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const marketplace = require('@hubspot/cli-lib/api/marketplace');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const ModuleDependencyValidator = require('../marketplaceValidators/module/ModuleDependencyValidator');
|
|
5
|
+
const { VALIDATION_RESULT } = require('../constants');
|
|
6
|
+
const { MODULE_PATH } = require('./validatorTestUtils');
|
|
7
|
+
|
|
8
|
+
jest.mock('@hubspot/cli-lib/api/marketplace');
|
|
9
|
+
|
|
10
|
+
const getMockDependencyResult = (customPaths = []) => {
|
|
11
|
+
const result = {
|
|
12
|
+
dependencies: [...customPaths],
|
|
13
|
+
};
|
|
14
|
+
return Promise.resolve(result);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('validators/marketplaceValidators/module/ModuleDependencyValidator', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
ModuleDependencyValidator.setRelativePath(MODULE_PATH);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('isExternalDep', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
ModuleDependencyValidator.setRelativePath(MODULE_PATH);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns true if dep is external to the provided absolute path', () => {
|
|
28
|
+
const isExternal = ModuleDependencyValidator.isExternalDep(
|
|
29
|
+
MODULE_PATH,
|
|
30
|
+
'SomeOtherFolder/In/The/DesignManager.css'
|
|
31
|
+
);
|
|
32
|
+
expect(isExternal).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns false if dep is not external to the provided absolute path', () => {
|
|
36
|
+
const isExternal = ModuleDependencyValidator.isExternalDep(
|
|
37
|
+
MODULE_PATH,
|
|
38
|
+
`${path.parse(MODULE_PATH).dir}/Internal/Folder/style.css`
|
|
39
|
+
);
|
|
40
|
+
expect(isExternal).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('validate', () => {
|
|
45
|
+
it('returns error if any referenced path is absolute', async () => {
|
|
46
|
+
marketplace.fetchModuleDependencies.mockReturnValue(
|
|
47
|
+
getMockDependencyResult(['/absolute/path'])
|
|
48
|
+
);
|
|
49
|
+
const validationErrors = await ModuleDependencyValidator.validate(
|
|
50
|
+
MODULE_PATH
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(validationErrors.length).toBe(1);
|
|
54
|
+
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns error if any referenced path is external to the theme', async () => {
|
|
58
|
+
marketplace.fetchModuleDependencies.mockReturnValue(
|
|
59
|
+
getMockDependencyResult(['../../external/file-3.js'])
|
|
60
|
+
);
|
|
61
|
+
const validationErrors = await ModuleDependencyValidator.validate(
|
|
62
|
+
MODULE_PATH
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(validationErrors.length).toBe(1);
|
|
66
|
+
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns no errors if paths are relative and internal', async () => {
|
|
70
|
+
marketplace.fetchModuleDependencies.mockReturnValue(
|
|
71
|
+
getMockDependencyResult(['module/style.css', 'module/another/test.js'])
|
|
72
|
+
);
|
|
73
|
+
const validationErrors = await ModuleDependencyValidator.validate(
|
|
74
|
+
MODULE_PATH
|
|
75
|
+
);
|
|
76
|
+
expect(validationErrors.length).toBe(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,84 +1,51 @@
|
|
|
1
|
-
const
|
|
2
|
-
const ModuleValidator = require('../marketplaceValidators/theme/ModuleValidator');
|
|
1
|
+
const ModuleValidator = require('../marketplaceValidators/module/ModuleValidator');
|
|
3
2
|
const { VALIDATION_RESULT } = require('../constants');
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
makeFindError,
|
|
7
|
-
THEME_PATH,
|
|
8
|
-
} = require('./validatorTestUtils');
|
|
3
|
+
const { MODULE_PATH } = require('./validatorTestUtils');
|
|
4
|
+
const marketplace = require('@hubspot/cli-lib/api/marketplace');
|
|
9
5
|
|
|
10
|
-
jest.mock('
|
|
6
|
+
jest.mock('@hubspot/cli-lib/api/marketplace');
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const findError = makeFindError('module');
|
|
15
|
-
|
|
16
|
-
describe('validators/marketplaceValidators/theme/ModuleValidator', () => {
|
|
8
|
+
describe('validators/marketplaceValidators/module/ModuleValidator', () => {
|
|
17
9
|
beforeEach(() => {
|
|
18
|
-
ModuleValidator.
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('returns error if module limit is exceeded', async () => {
|
|
22
|
-
const validationErrors = ModuleValidator.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 = ModuleValidator.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 = ModuleValidator.validate([
|
|
40
|
-
'module.module/module.html',
|
|
41
|
-
]);
|
|
42
|
-
expect(validationErrors.length).toBe(1);
|
|
43
|
-
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
10
|
+
ModuleValidator.setRelativePath(MODULE_PATH);
|
|
44
11
|
});
|
|
45
12
|
|
|
46
13
|
it('returns error if module meta.json file has invalid json', async () => {
|
|
47
|
-
|
|
14
|
+
marketplace.fetchModuleMeta.mockReturnValue(
|
|
15
|
+
Promise.resolve({ source: '{} bad json }' })
|
|
16
|
+
);
|
|
48
17
|
|
|
49
|
-
const validationErrors = ModuleValidator.validate(
|
|
50
|
-
'module.module/meta.json',
|
|
51
|
-
]);
|
|
18
|
+
const validationErrors = await ModuleValidator.validate(MODULE_PATH);
|
|
52
19
|
expect(validationErrors.length).toBe(1);
|
|
53
20
|
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
54
21
|
});
|
|
55
22
|
|
|
56
23
|
it('returns error if module meta.json file is missing a label field', async () => {
|
|
57
|
-
|
|
24
|
+
marketplace.fetchModuleMeta.mockReturnValue(
|
|
25
|
+
Promise.resolve({ source: '{ "icon": "woo" }' })
|
|
26
|
+
);
|
|
58
27
|
|
|
59
|
-
const validationErrors = ModuleValidator.validate(
|
|
60
|
-
'module.module/meta.json',
|
|
61
|
-
]);
|
|
28
|
+
const validationErrors = await ModuleValidator.validate(MODULE_PATH);
|
|
62
29
|
expect(validationErrors.length).toBe(1);
|
|
63
30
|
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
64
31
|
});
|
|
65
32
|
|
|
66
33
|
it('returns error if module meta.json file is missing an icon field', async () => {
|
|
67
|
-
|
|
34
|
+
marketplace.fetchModuleMeta.mockReturnValue(
|
|
35
|
+
Promise.resolve({ source: '{ "label": "yay" }' })
|
|
36
|
+
);
|
|
68
37
|
|
|
69
|
-
const validationErrors = ModuleValidator.validate(
|
|
70
|
-
'module.module/meta.json',
|
|
71
|
-
]);
|
|
38
|
+
const validationErrors = await ModuleValidator.validate(MODULE_PATH);
|
|
72
39
|
expect(validationErrors.length).toBe(1);
|
|
73
40
|
expect(validationErrors[0].result).toBe(VALIDATION_RESULT.FATAL);
|
|
74
41
|
});
|
|
75
42
|
|
|
76
43
|
it('returns no error if module meta.json file exists and has all required fields', async () => {
|
|
77
|
-
|
|
44
|
+
marketplace.fetchModuleMeta.mockReturnValue(
|
|
45
|
+
Promise.resolve({ source: '{ "label": "yay", "icon": "woo" }' })
|
|
46
|
+
);
|
|
78
47
|
|
|
79
|
-
const validationErrors = ModuleValidator.validate(
|
|
80
|
-
'module.module/meta.json',
|
|
81
|
-
]);
|
|
48
|
+
const validationErrors = await ModuleValidator.validate(MODULE_PATH);
|
|
82
49
|
expect(validationErrors.length).toBe(0);
|
|
83
50
|
});
|
|
84
51
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const RelativeValidator = require('../marketplaceValidators/RelativeValidator');
|
|
2
|
+
const { VALIDATION_RESULT } = require('../constants');
|
|
3
|
+
|
|
4
|
+
const Validator = new RelativeValidator({
|
|
5
|
+
name: 'Test validator',
|
|
6
|
+
key: 'validatorKey',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('validators/marketplaceValidators/RelativeValidator', () => {
|
|
10
|
+
it('getSuccess returns expected object', async () => {
|
|
11
|
+
const success = Validator.getSuccess();
|
|
12
|
+
|
|
13
|
+
expect(success.validatorKey).toBe('validatorKey');
|
|
14
|
+
expect(success.validatorName).toBe('Test validator');
|
|
15
|
+
expect(success.result).toBe(VALIDATION_RESULT.SUCCESS);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('getError returns expected object', async () => {
|
|
19
|
+
const errorObj = { key: 'errorkey', getCopy: () => 'Some error copy' };
|
|
20
|
+
const success = Validator.getError(errorObj);
|
|
21
|
+
|
|
22
|
+
expect(success.validatorKey).toBe('validatorKey');
|
|
23
|
+
expect(success.validatorName).toBe('Test validator');
|
|
24
|
+
expect(success.error).toBe('Some error copy');
|
|
25
|
+
expect(success.result).toBe(VALIDATION_RESULT.FATAL);
|
|
26
|
+
expect(success.key).toBe('validatorKey.errorkey');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -29,7 +29,7 @@ const findError = makeFindError('template');
|
|
|
29
29
|
|
|
30
30
|
describe('validators/marketplaceValidators/theme/TemplateValidator', () => {
|
|
31
31
|
beforeEach(() => {
|
|
32
|
-
TemplateValidator.
|
|
32
|
+
TemplateValidator.setAbsolutePath(THEME_PATH);
|
|
33
33
|
templates.isCodedFile.mockReturnValue(true);
|
|
34
34
|
});
|
|
35
35
|
|