@hubspot/cli 4.1.6 → 4.1.7-beta.1
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 +1 -1
- package/commands/sandbox/create.js +40 -84
- package/commands/sandbox/delete.js +11 -8
- package/commands/sandbox/sync.js +300 -0
- package/commands/sandbox.js +2 -0
- package/lib/projects.js +89 -45
- package/lib/prompts/accountsPrompt.js +11 -1
- package/lib/prompts/sandboxesPrompt.js +12 -14
- package/lib/sandboxes.js +217 -0
- package/package.json +5 -4
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
} = require('../../lib/commonOpts');
|
|
13
13
|
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
14
14
|
const { loadAndValidateOptions } = require('../../lib/validation');
|
|
15
|
-
const { getSandboxType } = require('../../lib/
|
|
15
|
+
const { getSandboxType } = require('../../lib/sandboxes');
|
|
16
16
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
17
17
|
|
|
18
18
|
const i18nKey = 'cli.commands.accounts.subcommands.list';
|
|
@@ -11,101 +11,26 @@ const Spinnies = require('spinnies');
|
|
|
11
11
|
const { createSandbox } = require('@hubspot/cli-lib/sandboxes');
|
|
12
12
|
const { loadAndValidateOptions } = require('../../lib/validation');
|
|
13
13
|
const { createSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt');
|
|
14
|
+
const {
|
|
15
|
+
getSandboxType,
|
|
16
|
+
sandboxCreatePersonalAccessKeyFlow,
|
|
17
|
+
} = require('../../lib/sandboxes');
|
|
14
18
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
15
19
|
const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
|
|
16
20
|
const {
|
|
17
21
|
debugErrorAndContext,
|
|
18
22
|
} = require('@hubspot/cli-lib/errorHandlers/standardErrors');
|
|
19
|
-
const {
|
|
20
|
-
ENVIRONMENTS,
|
|
21
|
-
PERSONAL_ACCESS_KEY_AUTH_METHOD,
|
|
22
|
-
DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
|
23
|
-
} = require('@hubspot/cli-lib/lib/constants');
|
|
24
|
-
const {
|
|
25
|
-
personalAccessKeyPrompt,
|
|
26
|
-
} = require('../../lib/prompts/personalAccessKeyPrompt');
|
|
27
|
-
const {
|
|
28
|
-
updateConfigWithPersonalAccessKey,
|
|
29
|
-
} = require('@hubspot/cli-lib/personalAccessKey');
|
|
23
|
+
const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
|
|
30
24
|
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
31
|
-
const {
|
|
32
|
-
getConfig,
|
|
33
|
-
writeConfig,
|
|
34
|
-
updateAccountConfig,
|
|
35
|
-
getAccountConfig,
|
|
36
|
-
} = require('@hubspot/cli-lib');
|
|
37
|
-
const {
|
|
38
|
-
enterAccountNamePrompt,
|
|
39
|
-
} = require('../../lib/prompts/enterAccountNamePrompt');
|
|
40
|
-
const {
|
|
41
|
-
setAsDefaultAccountPrompt,
|
|
42
|
-
} = require('../../lib/prompts/setAsDefaultAccountPrompt');
|
|
25
|
+
const { getAccountConfig } = require('@hubspot/cli-lib');
|
|
43
26
|
const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
44
27
|
const {
|
|
45
28
|
isMissingScopeError,
|
|
29
|
+
isSpecifiedError,
|
|
46
30
|
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
47
|
-
const { uiFeatureHighlight } = require('../../lib/ui');
|
|
48
31
|
|
|
49
32
|
const i18nKey = 'cli.commands.sandbox.subcommands.create';
|
|
50
33
|
|
|
51
|
-
const personalAccessKeyFlow = async (env, account, name) => {
|
|
52
|
-
const configData = await personalAccessKeyPrompt({ env, account });
|
|
53
|
-
const updatedConfig = await updateConfigWithPersonalAccessKey(configData);
|
|
54
|
-
|
|
55
|
-
if (!updatedConfig) {
|
|
56
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let validName = updatedConfig.name;
|
|
60
|
-
|
|
61
|
-
if (!updatedConfig.name) {
|
|
62
|
-
const nameForConfig = name
|
|
63
|
-
.toLowerCase()
|
|
64
|
-
.split(' ')
|
|
65
|
-
.join('-');
|
|
66
|
-
const { name: promptName } = await enterAccountNamePrompt(nameForConfig);
|
|
67
|
-
validName = promptName;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
updateAccountConfig({
|
|
71
|
-
...updatedConfig,
|
|
72
|
-
environment: updatedConfig.env,
|
|
73
|
-
tokenInfo: updatedConfig.auth.tokenInfo,
|
|
74
|
-
name: validName,
|
|
75
|
-
});
|
|
76
|
-
writeConfig();
|
|
77
|
-
|
|
78
|
-
const setAsDefault = await setAsDefaultAccountPrompt(validName);
|
|
79
|
-
|
|
80
|
-
logger.log('');
|
|
81
|
-
if (setAsDefault) {
|
|
82
|
-
logger.success(
|
|
83
|
-
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.setAsDefaultAccount`, {
|
|
84
|
-
accountName: validName,
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
} else {
|
|
88
|
-
const config = getConfig();
|
|
89
|
-
logger.info(
|
|
90
|
-
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.keepingCurrentDefault`, {
|
|
91
|
-
accountName: config.defaultPortal,
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
logger.success(
|
|
96
|
-
i18n(`${i18nKey}.success.configFileUpdated`, {
|
|
97
|
-
configFilename: DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
|
98
|
-
authMethod: PERSONAL_ACCESS_KEY_AUTH_METHOD.name,
|
|
99
|
-
account: validName,
|
|
100
|
-
})
|
|
101
|
-
);
|
|
102
|
-
uiFeatureHighlight([
|
|
103
|
-
'accountsUseCommand',
|
|
104
|
-
'accountOption',
|
|
105
|
-
'accountsListCommand',
|
|
106
|
-
]);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
34
|
exports.command = 'create [--name]';
|
|
110
35
|
exports.describe = i18n(`${i18nKey}.describe`);
|
|
111
36
|
|
|
@@ -122,6 +47,21 @@ exports.handler = async options => {
|
|
|
122
47
|
|
|
123
48
|
trackCommandUsage('sandbox-create', null, accountId);
|
|
124
49
|
|
|
50
|
+
if (
|
|
51
|
+
accountConfig.sandboxAccountType &&
|
|
52
|
+
accountConfig.sandboxAccountType !== null
|
|
53
|
+
) {
|
|
54
|
+
trackCommandUsage('sandbox-create', { successful: false }, accountId);
|
|
55
|
+
|
|
56
|
+
logger.error(
|
|
57
|
+
i18n(`${i18nKey}.failure.creatingWithinSandbox`, {
|
|
58
|
+
sandboxType: getSandboxType(accountConfig.sandboxAccountType),
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
process.exit(EXIT_CODES.ERROR);
|
|
63
|
+
}
|
|
64
|
+
|
|
125
65
|
let namePrompt;
|
|
126
66
|
|
|
127
67
|
logger.log(i18n(`${i18nKey}.sandboxLimitation`));
|
|
@@ -176,13 +116,29 @@ exports.handler = async options => {
|
|
|
176
116
|
url,
|
|
177
117
|
})
|
|
178
118
|
);
|
|
179
|
-
} else
|
|
119
|
+
} else if (
|
|
120
|
+
isSpecifiedError(
|
|
121
|
+
err,
|
|
122
|
+
400,
|
|
123
|
+
'VALIDATION_ERROR',
|
|
124
|
+
'SandboxErrors.NUM_DEVELOPMENT_SANDBOXES_LIMIT_EXCEEDED_ERROR'
|
|
125
|
+
) &&
|
|
126
|
+
err.error &&
|
|
127
|
+
err.error.message
|
|
128
|
+
) {
|
|
129
|
+
logger.log('');
|
|
180
130
|
logger.error(err.error.message);
|
|
131
|
+
} else {
|
|
132
|
+
logErrorInstance(err);
|
|
181
133
|
}
|
|
182
134
|
process.exit(EXIT_CODES.ERROR);
|
|
183
135
|
}
|
|
184
136
|
try {
|
|
185
|
-
await
|
|
137
|
+
await sandboxCreatePersonalAccessKeyFlow(
|
|
138
|
+
env,
|
|
139
|
+
result.sandboxHubId,
|
|
140
|
+
result.name
|
|
141
|
+
);
|
|
186
142
|
process.exit(EXIT_CODES.SUCCESS);
|
|
187
143
|
} catch (err) {
|
|
188
144
|
logErrorInstance(err);
|
|
@@ -11,7 +11,7 @@ const { loadAndValidateOptions } = require('../../lib/validation');
|
|
|
11
11
|
const {
|
|
12
12
|
debugErrorAndContext,
|
|
13
13
|
} = require('@hubspot/cli-lib/errorHandlers/standardErrors');
|
|
14
|
-
|
|
14
|
+
const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
|
|
15
15
|
const { deleteSandbox } = require('@hubspot/cli-lib/sandboxes');
|
|
16
16
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
17
17
|
const { getConfig, getEnv } = require('@hubspot/cli-lib');
|
|
@@ -26,12 +26,12 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
|
26
26
|
const { promptUser } = require('../../lib/prompts/promptUtils');
|
|
27
27
|
const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
28
28
|
const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
|
|
29
|
+
const {
|
|
30
|
+
isSpecifiedError,
|
|
31
|
+
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
29
32
|
|
|
30
33
|
const i18nKey = 'cli.commands.sandbox.subcommands.delete';
|
|
31
34
|
|
|
32
|
-
const SANDBOX_NOT_FOUND = 'SandboxErrors.SANDBOX_NOT_FOUND';
|
|
33
|
-
const OBJECT_NOT_FOUND = 'OBJECT_NOT_FOUND';
|
|
34
|
-
|
|
35
35
|
exports.command = 'delete [--account]';
|
|
36
36
|
exports.describe = i18n(`${i18nKey}.describe`);
|
|
37
37
|
|
|
@@ -147,9 +147,12 @@ exports.handler = async options => {
|
|
|
147
147
|
);
|
|
148
148
|
|
|
149
149
|
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
isSpecifiedError(
|
|
151
|
+
err,
|
|
152
|
+
404,
|
|
153
|
+
'OBJECT_NOT_FOUND',
|
|
154
|
+
'SandboxErrors.SANDBOX_NOT_FOUND'
|
|
155
|
+
)
|
|
153
156
|
) {
|
|
154
157
|
logger.log('');
|
|
155
158
|
logger.warn(
|
|
@@ -167,7 +170,7 @@ exports.handler = async options => {
|
|
|
167
170
|
}
|
|
168
171
|
process.exit(EXIT_CODES.SUCCESS);
|
|
169
172
|
} else {
|
|
170
|
-
|
|
173
|
+
logErrorInstance(err);
|
|
171
174
|
}
|
|
172
175
|
process.exit(EXIT_CODES.ERROR);
|
|
173
176
|
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
const {
|
|
2
|
+
addAccountOptions,
|
|
3
|
+
addConfigOptions,
|
|
4
|
+
getAccountId,
|
|
5
|
+
addUseEnvironmentOptions,
|
|
6
|
+
addTestingOptions,
|
|
7
|
+
} = require('../../lib/commonOpts');
|
|
8
|
+
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
9
|
+
const { logger } = require('@hubspot/cli-lib/logger');
|
|
10
|
+
const Spinnies = require('spinnies');
|
|
11
|
+
const { initiateSync } = require('@hubspot/cli-lib/sandboxes');
|
|
12
|
+
const { loadAndValidateOptions } = require('../../lib/validation');
|
|
13
|
+
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
14
|
+
const { logErrorInstance } = require('@hubspot/cli-lib/errorHandlers');
|
|
15
|
+
const { ENVIRONMENTS } = require('@hubspot/cli-lib/lib/constants');
|
|
16
|
+
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
17
|
+
const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
|
|
18
|
+
const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
19
|
+
const { promptUser } = require('../../lib/prompts/promptUtils');
|
|
20
|
+
const { uiLine } = require('../../lib/ui');
|
|
21
|
+
const {
|
|
22
|
+
getAccountName,
|
|
23
|
+
getAvailableSyncTypes,
|
|
24
|
+
pollSyncTaskStatus,
|
|
25
|
+
} = require('../../lib/sandboxes');
|
|
26
|
+
const {
|
|
27
|
+
isMissingScopeError,
|
|
28
|
+
isSpecifiedError,
|
|
29
|
+
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
30
|
+
|
|
31
|
+
const i18nKey = 'cli.commands.sandbox.subcommands.sync';
|
|
32
|
+
|
|
33
|
+
exports.command = 'sync';
|
|
34
|
+
exports.describe = i18n(`${i18nKey}.describe`);
|
|
35
|
+
|
|
36
|
+
exports.handler = async options => {
|
|
37
|
+
await loadAndValidateOptions(options);
|
|
38
|
+
|
|
39
|
+
const { force } = options; // For scripting purposes
|
|
40
|
+
const accountId = getAccountId(options);
|
|
41
|
+
const accountConfig = getAccountConfig(accountId);
|
|
42
|
+
const spinnies = new Spinnies({
|
|
43
|
+
succeedColor: 'white',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
trackCommandUsage('sandbox-sync', null, accountId);
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
// Check if default account is a sandbox, otherwise exit
|
|
50
|
+
// sandboxAccountType is null for non-sandbox portals, and one of 'DEVELOPER' or 'STANDARD' for sandbox portals. Undefined is to handle older config entries.
|
|
51
|
+
accountConfig.sandboxAccountType === undefined ||
|
|
52
|
+
accountConfig.sandboxAccountType === null
|
|
53
|
+
) {
|
|
54
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
55
|
+
|
|
56
|
+
logger.error(i18n(`${i18nKey}.failure.notSandbox`));
|
|
57
|
+
|
|
58
|
+
process.exit(EXIT_CODES.ERROR);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Verify parent account exists in the config
|
|
62
|
+
let parentAccountId = accountConfig.parentAccountId || undefined;
|
|
63
|
+
if (!parentAccountId || !getAccountId({ account: parentAccountId })) {
|
|
64
|
+
logger.log('');
|
|
65
|
+
logger.error(
|
|
66
|
+
i18n(`${i18nKey}.failure.missingParentPortal`, {
|
|
67
|
+
sandboxName: getAccountName(accountConfig),
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
process.exit(EXIT_CODES.ERROR);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const parentAccountConfig = getAccountConfig(parentAccountId);
|
|
74
|
+
const isDevelopmentSandbox = accountConfig.sandboxAccountType === 'DEVELOPER';
|
|
75
|
+
const isStandardSandbox = accountConfig.sandboxAccountType === 'STANDARD';
|
|
76
|
+
|
|
77
|
+
if (isDevelopmentSandbox) {
|
|
78
|
+
logger.log(i18n(`${i18nKey}.info.developmentSandbox`));
|
|
79
|
+
logger.log(
|
|
80
|
+
i18n(`${i18nKey}.info.sync`, {
|
|
81
|
+
parentAccountName: getAccountName(parentAccountConfig),
|
|
82
|
+
sandboxName: getAccountName(accountConfig),
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
uiLine();
|
|
86
|
+
logger.warn(i18n(`${i18nKey}.warning.developmentSandbox`));
|
|
87
|
+
uiLine();
|
|
88
|
+
logger.log('');
|
|
89
|
+
|
|
90
|
+
if (!force) {
|
|
91
|
+
// Skip confirmation if force flag is present.
|
|
92
|
+
const { confirmSandboxSyncPrompt: confirmed } = await promptUser([
|
|
93
|
+
{
|
|
94
|
+
name: 'confirmSandboxSyncPrompt',
|
|
95
|
+
type: 'confirm',
|
|
96
|
+
message: i18n(`${i18nKey}.confirm.developmentSandbox`, {
|
|
97
|
+
parentAccountName: getAccountName(parentAccountConfig),
|
|
98
|
+
sandboxName: getAccountName(accountConfig),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
]);
|
|
102
|
+
if (!confirmed) {
|
|
103
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} else if (isStandardSandbox) {
|
|
107
|
+
const standardSyncUrl = `${getHubSpotWebsiteOrigin(
|
|
108
|
+
getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
|
|
109
|
+
)}/sandboxes-developer/${parentAccountId}/sync?step=select_sync_path&id=${parentAccountId}_${accountId}`;
|
|
110
|
+
|
|
111
|
+
logger.log(
|
|
112
|
+
i18n(`${i18nKey}.info.standardSandbox`, {
|
|
113
|
+
url: standardSyncUrl,
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
logger.log(
|
|
117
|
+
i18n(`${i18nKey}.info.sync`, {
|
|
118
|
+
parentAccountName: getAccountName(parentAccountConfig),
|
|
119
|
+
sandboxName: getAccountName(accountConfig),
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
uiLine();
|
|
123
|
+
logger.warn(i18n(`${i18nKey}.warning.standardSandbox`));
|
|
124
|
+
uiLine();
|
|
125
|
+
logger.log('');
|
|
126
|
+
|
|
127
|
+
if (!force) {
|
|
128
|
+
// Skip confirmation if force flag is present.
|
|
129
|
+
const { confirmSandboxSyncPrompt: confirmed } = await promptUser([
|
|
130
|
+
{
|
|
131
|
+
name: 'confirmSandboxSyncPrompt',
|
|
132
|
+
type: 'confirm',
|
|
133
|
+
message: i18n(`${i18nKey}.confirm.standardSandbox`, {
|
|
134
|
+
parentAccountName: getAccountName(parentAccountConfig),
|
|
135
|
+
sandboxName: getAccountName(accountConfig),
|
|
136
|
+
}),
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
if (!confirmed) {
|
|
140
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
logger.error('Sync must be run in a sandbox account.');
|
|
145
|
+
process.exit(EXIT_CODES.ERROR);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let initiateSyncResponse;
|
|
149
|
+
|
|
150
|
+
const baseUrl = getHubSpotWebsiteOrigin(
|
|
151
|
+
getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD
|
|
152
|
+
);
|
|
153
|
+
const syncStatusUrl = `${baseUrl}/sandboxes-developer/${parentAccountId}/${
|
|
154
|
+
isDevelopmentSandbox ? 'development' : 'standard'
|
|
155
|
+
}`;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
logger.log('');
|
|
159
|
+
spinnies.add('sandboxSync', {
|
|
160
|
+
text: i18n(`${i18nKey}.loading.startSync`),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Fetches sync types based on default account. Parent account required for fetch
|
|
164
|
+
const tasks = await getAvailableSyncTypes(
|
|
165
|
+
parentAccountConfig,
|
|
166
|
+
accountConfig
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
initiateSyncResponse = await initiateSync(
|
|
170
|
+
parentAccountId,
|
|
171
|
+
accountId,
|
|
172
|
+
tasks,
|
|
173
|
+
accountId
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
logger.log(i18n(`${i18nKey}.info.earlyExit`));
|
|
177
|
+
logger.log('');
|
|
178
|
+
spinnies.succeed('sandboxSync', {
|
|
179
|
+
text: i18n(`${i18nKey}.loading.succeed`),
|
|
180
|
+
});
|
|
181
|
+
} catch (err) {
|
|
182
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
183
|
+
|
|
184
|
+
spinnies.fail('sandboxSync', {
|
|
185
|
+
text: i18n(`${i18nKey}.loading.fail`),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
logger.log('');
|
|
189
|
+
if (isMissingScopeError(err)) {
|
|
190
|
+
logger.error(
|
|
191
|
+
i18n(`${i18nKey}.failure.missingScopes`, {
|
|
192
|
+
accountName: getAccountName(parentAccountConfig),
|
|
193
|
+
})
|
|
194
|
+
);
|
|
195
|
+
} else if (
|
|
196
|
+
isSpecifiedError(
|
|
197
|
+
err,
|
|
198
|
+
429,
|
|
199
|
+
'RATE_LIMITS',
|
|
200
|
+
'sandboxes-sync-api.SYNC_IN_PROGRESS'
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
logger.error(
|
|
204
|
+
i18n(`${i18nKey}.failure.syncInProgress`, {
|
|
205
|
+
url: `${baseUrl}/sandboxes-developer/${parentAccountId}/syncactivitylog`,
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
} else if (
|
|
209
|
+
isSpecifiedError(
|
|
210
|
+
err,
|
|
211
|
+
403,
|
|
212
|
+
'BANNED',
|
|
213
|
+
'sandboxes-sync-api.SYNC_NOT_ALLOWED_INVALID_USERID'
|
|
214
|
+
)
|
|
215
|
+
) {
|
|
216
|
+
// This will only trigger if a user is not a super admin of the target account.
|
|
217
|
+
logger.error(
|
|
218
|
+
i18n(`${i18nKey}.failure.notSuperAdmin`, {
|
|
219
|
+
account: getAccountName(accountConfig),
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
} else if (
|
|
223
|
+
isSpecifiedError(
|
|
224
|
+
err,
|
|
225
|
+
404,
|
|
226
|
+
'OBJECT_NOT_FOUND',
|
|
227
|
+
'SandboxErrors.SANDBOX_NOT_FOUND'
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
logger.error(
|
|
231
|
+
i18n(`${i18nKey}.failure.objectNotFound`, {
|
|
232
|
+
account: getAccountName(accountConfig),
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
logErrorInstance(err);
|
|
237
|
+
}
|
|
238
|
+
logger.log('');
|
|
239
|
+
|
|
240
|
+
process.exit(EXIT_CODES.ERROR);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
logger.log('');
|
|
245
|
+
logger.log('Sync progress:');
|
|
246
|
+
// Poll sync task status to show progress bars
|
|
247
|
+
await pollSyncTaskStatus(
|
|
248
|
+
parentAccountId,
|
|
249
|
+
initiateSyncResponse.id,
|
|
250
|
+
syncStatusUrl
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
logger.log('');
|
|
254
|
+
spinnies.add('syncComplete', {
|
|
255
|
+
text: i18n(`${i18nKey}.polling.syncing`),
|
|
256
|
+
});
|
|
257
|
+
spinnies.succeed('syncComplete', {
|
|
258
|
+
text: i18n(`${i18nKey}.polling.succeed`),
|
|
259
|
+
});
|
|
260
|
+
logger.log('');
|
|
261
|
+
logger.log(
|
|
262
|
+
i18n(`${i18nKey}.info.syncStatus`, {
|
|
263
|
+
url: syncStatusUrl,
|
|
264
|
+
})
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
268
|
+
} catch (err) {
|
|
269
|
+
// If polling fails at this point, we do not track a failed sync since it is running in the background.
|
|
270
|
+
logErrorInstance(err);
|
|
271
|
+
|
|
272
|
+
spinnies.add('syncComplete', {
|
|
273
|
+
text: i18n(`${i18nKey}.polling.syncing`),
|
|
274
|
+
});
|
|
275
|
+
spinnies.fail('syncComplete', {
|
|
276
|
+
text: i18n(`${i18nKey}.polling.fail`, {
|
|
277
|
+
url: syncStatusUrl,
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
process.exit(EXIT_CODES.ERROR);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
exports.builder = yargs => {
|
|
286
|
+
yargs.option('f', {
|
|
287
|
+
type: 'boolean',
|
|
288
|
+
alias: 'force',
|
|
289
|
+
describe: i18n(`${i18nKey}.examples.force`),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
yargs.example([['$0 sandbox sync', i18n(`${i18nKey}.examples.default`)]]);
|
|
293
|
+
|
|
294
|
+
addConfigOptions(yargs, true);
|
|
295
|
+
addAccountOptions(yargs, true);
|
|
296
|
+
addUseEnvironmentOptions(yargs, true);
|
|
297
|
+
addTestingOptions(yargs, true);
|
|
298
|
+
|
|
299
|
+
return yargs;
|
|
300
|
+
};
|
package/commands/sandbox.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts');
|
|
2
2
|
const create = require('./sandbox/create');
|
|
3
3
|
const del = require('./sandbox/delete');
|
|
4
|
+
const sync = require('./sandbox/sync');
|
|
4
5
|
|
|
5
6
|
// const i18nKey = 'cli.commands.sandbox';
|
|
6
7
|
|
|
@@ -14,6 +15,7 @@ exports.builder = yargs => {
|
|
|
14
15
|
yargs
|
|
15
16
|
.command(create)
|
|
16
17
|
.command(del)
|
|
18
|
+
.command(sync)
|
|
17
19
|
.demandCommand(1, '');
|
|
18
20
|
|
|
19
21
|
return yargs;
|
package/lib/projects.js
CHANGED
|
@@ -18,6 +18,8 @@ const {
|
|
|
18
18
|
PROJECT_BUILD_TEXT,
|
|
19
19
|
PROJECT_DEPLOY_TEXT,
|
|
20
20
|
PROJECT_CONFIG_FILE,
|
|
21
|
+
PROJECT_TASK_TYPES,
|
|
22
|
+
SPINNER_STATUS,
|
|
21
23
|
} = require('@hubspot/cli-lib/lib/constants');
|
|
22
24
|
const {
|
|
23
25
|
createProject,
|
|
@@ -361,7 +363,7 @@ const handleProjectUpload = async (
|
|
|
361
363
|
archive.pipe(output);
|
|
362
364
|
|
|
363
365
|
archive.directory(srcDir, false, file =>
|
|
364
|
-
shouldIgnoreFile(file.name) ? false : file
|
|
366
|
+
shouldIgnoreFile(file.name, true) ? false : file
|
|
365
367
|
);
|
|
366
368
|
|
|
367
369
|
archive.finalize();
|
|
@@ -374,6 +376,8 @@ const makePollTaskStatusFunc = ({
|
|
|
374
376
|
statusStrings,
|
|
375
377
|
linkToHubSpot,
|
|
376
378
|
}) => {
|
|
379
|
+
const i18nKey = 'cli.commands.project.lib.makePollTaskStatusFunc';
|
|
380
|
+
|
|
377
381
|
const isTaskComplete = task => {
|
|
378
382
|
if (
|
|
379
383
|
!task[statusText.SUBTASK_KEY].length ||
|
|
@@ -410,29 +414,60 @@ const makePollTaskStatusFunc = ({
|
|
|
410
414
|
structureFn(accountId, taskName, taskId),
|
|
411
415
|
]);
|
|
412
416
|
|
|
413
|
-
const
|
|
414
|
-
(
|
|
417
|
+
const tasksById = initialTaskStatus[statusText.SUBTASK_KEY].reduce(
|
|
418
|
+
(acc, task) => {
|
|
419
|
+
const type = task[statusText.TYPE_KEY];
|
|
420
|
+
if (type !== 'APP_ID' && type !== 'SERVERLESS_PKG') {
|
|
421
|
+
acc[task.id] = task;
|
|
422
|
+
}
|
|
423
|
+
return acc;
|
|
424
|
+
},
|
|
425
|
+
{}
|
|
415
426
|
);
|
|
416
427
|
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
428
|
+
const structuredTasks = Object.keys(taskStructure).map(key => {
|
|
429
|
+
return {
|
|
430
|
+
...tasksById[key],
|
|
431
|
+
subtasks: taskStructure[key]
|
|
432
|
+
.filter(taskId => Boolean(tasksById[taskId]))
|
|
433
|
+
.map(taskId => tasksById[taskId]),
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const numComponents = structuredTasks.length;
|
|
438
|
+
const componentCountText = i18n(
|
|
439
|
+
numComponents === 1
|
|
440
|
+
? `${i18nKey}.componentCountSingular`
|
|
441
|
+
: `${i18nKey}.componentCount`,
|
|
442
|
+
{ numComponents }
|
|
443
|
+
);
|
|
421
444
|
|
|
422
445
|
spinnies.update('overallTaskStatus', {
|
|
423
|
-
text: `${statusStrings.INITIALIZE(taskName)}${componentCountText}`,
|
|
446
|
+
text: `${statusStrings.INITIALIZE(taskName)}\n${componentCountText}\n`,
|
|
424
447
|
});
|
|
425
448
|
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
449
|
+
const addTaskSpinner = (task, indent, newline) => {
|
|
450
|
+
const taskName = task[statusText.SUBTASK_NAME_KEY];
|
|
451
|
+
const taskType = task[statusText.TYPE_KEY];
|
|
452
|
+
const formattedTaskType = PROJECT_TASK_TYPES[taskType]
|
|
453
|
+
? `[${PROJECT_TASK_TYPES[taskType]}]`
|
|
454
|
+
: '';
|
|
455
|
+
const text = `${statusText.STATUS_TEXT} ${chalk.bold(
|
|
456
|
+
taskName
|
|
457
|
+
)} ${formattedTaskType} ...${newline ? '\n' : ''}`;
|
|
458
|
+
|
|
459
|
+
spinnies.add(task.id, {
|
|
460
|
+
text,
|
|
461
|
+
indent,
|
|
434
462
|
});
|
|
435
|
-
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
structuredTasks.forEach(task => {
|
|
466
|
+
addTaskSpinner(task, 2, !task.subtasks || task.subtasks.length === 0);
|
|
467
|
+
task.subtasks.forEach((subtask, i) =>
|
|
468
|
+
addTaskSpinner(subtask, 4, i === task.subtasks.length - 1)
|
|
469
|
+
);
|
|
470
|
+
});
|
|
436
471
|
|
|
437
472
|
return new Promise((resolve, reject) => {
|
|
438
473
|
const pollInterval = setInterval(async () => {
|
|
@@ -444,34 +479,43 @@ const makePollTaskStatusFunc = ({
|
|
|
444
479
|
|
|
445
480
|
if (spinnies.hasActiveSpinners()) {
|
|
446
481
|
subTaskStatus.forEach(subTask => {
|
|
447
|
-
const
|
|
482
|
+
const { id, status } = subTask;
|
|
483
|
+
const spinner = spinnies.pick(id);
|
|
448
484
|
|
|
449
|
-
if (!
|
|
485
|
+
if (!spinner || spinner.status !== SPINNER_STATUS.SPINNING) {
|
|
450
486
|
return;
|
|
451
487
|
}
|
|
452
488
|
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
489
|
+
const topLevelTask = structuredTasks.find(t => t.id == id);
|
|
490
|
+
|
|
491
|
+
if (
|
|
492
|
+
status === statusText.STATES.SUCCESS ||
|
|
493
|
+
status === statusText.STATES.FAILURE
|
|
494
|
+
) {
|
|
495
|
+
const taskStatusText =
|
|
496
|
+
subTask.status === statusText.STATES.SUCCESS
|
|
497
|
+
? i18n(`${i18nKey}.successStatusText`)
|
|
498
|
+
: i18n(`${i18nKey}.failedStatusText`);
|
|
499
|
+
const hasNewline =
|
|
500
|
+
spinner.text.includes('\n') || Boolean(topLevelTask);
|
|
501
|
+
const updatedText = `${spinner.text.replace(
|
|
502
|
+
'\n',
|
|
503
|
+
''
|
|
504
|
+
)} ${taskStatusText}${hasNewline ? '\n' : ''}`;
|
|
505
|
+
|
|
506
|
+
status === statusText.STATES.SUCCESS
|
|
507
|
+
? spinnies.succeed(id, { text: updatedText })
|
|
508
|
+
: spinnies.fail(id, { text: updatedText });
|
|
509
|
+
|
|
510
|
+
if (topLevelTask) {
|
|
511
|
+
topLevelTask.subtasks.forEach(currentSubtask =>
|
|
512
|
+
spinnies.remove(currentSubtask.id)
|
|
513
|
+
);
|
|
514
|
+
}
|
|
467
515
|
}
|
|
468
516
|
});
|
|
469
517
|
|
|
470
518
|
if (isTaskComplete(taskStatus)) {
|
|
471
|
-
// subTaskStatus.forEach(subTask => {
|
|
472
|
-
// spinnies.remove(subTask[statusText.SUBTASK_NAME_KEY]);
|
|
473
|
-
// });
|
|
474
|
-
|
|
475
519
|
if (status === statusText.STATES.SUCCESS) {
|
|
476
520
|
spinnies.succeed('overallTaskStatus', {
|
|
477
521
|
text: statusStrings.SUCCESS(taskName),
|
|
@@ -481,7 +525,7 @@ const makePollTaskStatusFunc = ({
|
|
|
481
525
|
text: statusStrings.FAIL(taskName),
|
|
482
526
|
});
|
|
483
527
|
|
|
484
|
-
const
|
|
528
|
+
const failedSubtasks = subTaskStatus.filter(
|
|
485
529
|
subtask => subtask.status === 'FAILURE'
|
|
486
530
|
);
|
|
487
531
|
|
|
@@ -489,19 +533,19 @@ const makePollTaskStatusFunc = ({
|
|
|
489
533
|
logger.log(
|
|
490
534
|
`${statusStrings.SUBTASK_FAIL(
|
|
491
535
|
displayId,
|
|
492
|
-
|
|
493
|
-
?
|
|
494
|
-
:
|
|
536
|
+
failedSubtasks.length === 1
|
|
537
|
+
? failedSubtasks[0][statusText.SUBTASK_NAME_KEY]
|
|
538
|
+
: failedSubtasks.length + ' components'
|
|
495
539
|
)}\n`
|
|
496
540
|
);
|
|
497
541
|
logger.log('See below for a summary of errors.');
|
|
498
542
|
uiLine();
|
|
499
543
|
|
|
500
|
-
|
|
544
|
+
failedSubtasks.forEach(subTask => {
|
|
501
545
|
logger.log(
|
|
502
|
-
`\n--- ${chalk.bold(
|
|
503
|
-
statusText.
|
|
504
|
-
} with the following error ---`
|
|
546
|
+
`\n--- ${chalk.bold(
|
|
547
|
+
subTask[statusText.SUBTASK_NAME_KEY]
|
|
548
|
+
)} failed with the following error ---`
|
|
505
549
|
);
|
|
506
550
|
logger.error(subTask.errorMessage);
|
|
507
551
|
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
const { updateDefaultAccount } = require('@hubspot/cli-lib/lib/config');
|
|
2
2
|
const { promptUser } = require('./promptUtils');
|
|
3
3
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
4
|
-
const {
|
|
4
|
+
const { getSandboxType } = require('../sandboxes');
|
|
5
|
+
|
|
6
|
+
const mapAccountChoices = portals =>
|
|
7
|
+
portals.map(p => {
|
|
8
|
+
const isSandbox = p.sandboxAccountType && p.sandboxAccountType !== null;
|
|
9
|
+
const sandboxName = `[${getSandboxType(p.sandboxAccountType)} sandbox] `;
|
|
10
|
+
return {
|
|
11
|
+
name: `${p.name} ${isSandbox ? sandboxName : ''}(${p.portalId})`,
|
|
12
|
+
value: p.name || p.portalId,
|
|
13
|
+
};
|
|
14
|
+
});
|
|
5
15
|
|
|
6
16
|
const i18nKey = 'cli.commands.accounts.subcommands.use';
|
|
7
17
|
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
const { promptUser } = require('./promptUtils');
|
|
2
2
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
3
|
+
const { getSandboxType } = require('../sandboxes');
|
|
3
4
|
|
|
4
5
|
const i18nKey = 'cli.lib.prompts.sandboxesPrompt';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
17
|
-
});
|
|
7
|
+
const mapSandboxAccountChoices = portals =>
|
|
8
|
+
portals
|
|
9
|
+
.filter(p => p.sandboxAccountType && p.sandboxAccountType !== null)
|
|
10
|
+
.map(p => {
|
|
11
|
+
const sandboxName = `[${getSandboxType(p.sandboxAccountType)} sandbox] `;
|
|
12
|
+
return {
|
|
13
|
+
name: `${p.name} ${sandboxName}(${p.portalId})`,
|
|
14
|
+
value: p.name || p.portalId,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
18
17
|
|
|
19
18
|
const createSandboxPrompt = () => {
|
|
20
19
|
return promptUser([
|
|
@@ -44,7 +43,7 @@ const deleteSandboxPrompt = (config, promptParentAccount = false) => {
|
|
|
44
43
|
type: 'list',
|
|
45
44
|
look: false,
|
|
46
45
|
pageSize: 20,
|
|
47
|
-
choices:
|
|
46
|
+
choices: mapSandboxAccountChoices(config.portals),
|
|
48
47
|
default: config.defaultPortal,
|
|
49
48
|
},
|
|
50
49
|
]);
|
|
@@ -54,5 +53,4 @@ module.exports = {
|
|
|
54
53
|
createSandboxPrompt,
|
|
55
54
|
deleteSandboxPrompt,
|
|
56
55
|
getSandboxType,
|
|
57
|
-
mapAccountChoices,
|
|
58
56
|
};
|
package/lib/sandboxes.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const cliProgress = require('cli-progress');
|
|
2
|
+
const {
|
|
3
|
+
getConfig,
|
|
4
|
+
writeConfig,
|
|
5
|
+
updateAccountConfig,
|
|
6
|
+
} = require('@hubspot/cli-lib');
|
|
7
|
+
const {
|
|
8
|
+
DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
|
9
|
+
PERSONAL_ACCESS_KEY_AUTH_METHOD,
|
|
10
|
+
} = require('@hubspot/cli-lib/lib/constants');
|
|
11
|
+
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
12
|
+
const { logger } = require('@hubspot/cli-lib/logger');
|
|
13
|
+
const {
|
|
14
|
+
updateConfigWithPersonalAccessKey,
|
|
15
|
+
} = require('@hubspot/cli-lib/personalAccessKey');
|
|
16
|
+
const { EXIT_CODES } = require('./enums/exitCodes');
|
|
17
|
+
const { enterAccountNamePrompt } = require('./prompts/enterAccountNamePrompt');
|
|
18
|
+
const {
|
|
19
|
+
personalAccessKeyPrompt,
|
|
20
|
+
} = require('./prompts/personalAccessKeyPrompt');
|
|
21
|
+
const {
|
|
22
|
+
setAsDefaultAccountPrompt,
|
|
23
|
+
} = require('./prompts/setAsDefaultAccountPrompt');
|
|
24
|
+
const { uiFeatureHighlight } = require('./ui');
|
|
25
|
+
const { fetchTaskStatus, fetchTypes } = require('@hubspot/cli-lib/sandboxes');
|
|
26
|
+
const { handleExit, handleKeypress } = require('@hubspot/cli-lib/lib/process');
|
|
27
|
+
|
|
28
|
+
const getSandboxType = type =>
|
|
29
|
+
type === 'DEVELOPER' ? 'development' : 'standard';
|
|
30
|
+
|
|
31
|
+
function getAccountName(config) {
|
|
32
|
+
const isSandbox =
|
|
33
|
+
config.sandboxAccountType && config.sandboxAccountType !== null;
|
|
34
|
+
const sandboxName = `[${getSandboxType(config.sandboxAccountType)} sandbox] `;
|
|
35
|
+
return `${config.name} ${isSandbox ? sandboxName : ''}(${config.portalId})`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fetches available sync types for a given sandbox portal
|
|
39
|
+
async function getAvailableSyncTypes(parentAccountConfig, config) {
|
|
40
|
+
const parentPortalId = parentAccountConfig.portalId;
|
|
41
|
+
const portalId = config.portalId;
|
|
42
|
+
const syncTypes = await fetchTypes(parentPortalId, portalId);
|
|
43
|
+
return syncTypes.map(t => ({ type: t.name }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sandboxCreatePersonalAccessKeyFlow = async (env, account, name) => {
|
|
47
|
+
const configData = await personalAccessKeyPrompt({ env, account });
|
|
48
|
+
const updatedConfig = await updateConfigWithPersonalAccessKey(configData);
|
|
49
|
+
|
|
50
|
+
if (!updatedConfig) {
|
|
51
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let validName = updatedConfig.name;
|
|
55
|
+
|
|
56
|
+
if (!updatedConfig.name) {
|
|
57
|
+
const nameForConfig = name
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.split(' ')
|
|
60
|
+
.join('-');
|
|
61
|
+
const { name: promptName } = await enterAccountNamePrompt(nameForConfig);
|
|
62
|
+
validName = promptName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
updateAccountConfig({
|
|
66
|
+
...updatedConfig,
|
|
67
|
+
environment: updatedConfig.env,
|
|
68
|
+
tokenInfo: updatedConfig.auth.tokenInfo,
|
|
69
|
+
name: validName,
|
|
70
|
+
});
|
|
71
|
+
writeConfig();
|
|
72
|
+
|
|
73
|
+
const setAsDefault = await setAsDefaultAccountPrompt(validName);
|
|
74
|
+
|
|
75
|
+
logger.log('');
|
|
76
|
+
if (setAsDefault) {
|
|
77
|
+
logger.success(
|
|
78
|
+
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.setAsDefaultAccount`, {
|
|
79
|
+
accountName: validName,
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
const config = getConfig();
|
|
84
|
+
logger.info(
|
|
85
|
+
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.keepingCurrentDefault`, {
|
|
86
|
+
accountName: config.defaultPortal,
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
logger.success(
|
|
91
|
+
i18n('cli.commands.sandbox.subcommands.create.success.configFileUpdated', {
|
|
92
|
+
configFilename: DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
|
93
|
+
authMethod: PERSONAL_ACCESS_KEY_AUTH_METHOD.name,
|
|
94
|
+
account: validName,
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
uiFeatureHighlight([
|
|
98
|
+
'accountsUseCommand',
|
|
99
|
+
'accountOption',
|
|
100
|
+
'accountsListCommand',
|
|
101
|
+
]);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const ACTIVE_TASK_POLL_INTERVAL = 1000;
|
|
105
|
+
|
|
106
|
+
const isTaskComplete = task => {
|
|
107
|
+
if (!task) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return task.status === 'COMPLETE';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Returns a promise to poll a sync task with taskId. Interval runs until sync task status is equal to 'COMPLETE'
|
|
114
|
+
function pollSyncTaskStatus(accountId, taskId, syncStatusUrl) {
|
|
115
|
+
const i18nKey = 'cli.commands.sandbox.subcommands.sync.types';
|
|
116
|
+
const multibar = new cliProgress.MultiBar(
|
|
117
|
+
{
|
|
118
|
+
hideCursor: true,
|
|
119
|
+
format: '[{bar}] {percentage}% | {taskType}',
|
|
120
|
+
gracefulExit: true,
|
|
121
|
+
},
|
|
122
|
+
cliProgress.Presets.rect
|
|
123
|
+
);
|
|
124
|
+
const mergeTasks = {
|
|
125
|
+
'lead-flows': 'forms', // lead-flows are a subset of forms. We combine these in the UI as a single item, so we want to merge here for consistency.
|
|
126
|
+
};
|
|
127
|
+
const barInstances = {};
|
|
128
|
+
let pollInterval;
|
|
129
|
+
// Handle manual exit for return key and ctrl+c
|
|
130
|
+
const onTerminate = () => {
|
|
131
|
+
clearInterval(pollInterval);
|
|
132
|
+
multibar.stop();
|
|
133
|
+
logger.log('');
|
|
134
|
+
logger.log('Exiting, sync will continue in the background.');
|
|
135
|
+
logger.log('');
|
|
136
|
+
logger.log(
|
|
137
|
+
i18n('cli.commands.sandbox.subcommands.sync.info.syncStatus', {
|
|
138
|
+
url: syncStatusUrl,
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
142
|
+
};
|
|
143
|
+
handleExit(onTerminate);
|
|
144
|
+
handleKeypress(key => {
|
|
145
|
+
if (
|
|
146
|
+
(key && key.ctrl && key.name == 'c') ||
|
|
147
|
+
key.name === 'enter' ||
|
|
148
|
+
key.name === 'return'
|
|
149
|
+
) {
|
|
150
|
+
onTerminate();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
pollInterval = setInterval(async () => {
|
|
155
|
+
const taskResult = await fetchTaskStatus(accountId, taskId).catch(reject);
|
|
156
|
+
if (taskResult.tasks) {
|
|
157
|
+
// Array of sync tasks, eg: workflows, pipelines, object-schemas, etc. with each task containing a status of 'PENDING', 'IN_PROGRESS', 'COMPLETE', and 'FAILURE'
|
|
158
|
+
for (const task of taskResult.tasks) {
|
|
159
|
+
// For each sync task, show a progress bar and increment bar each time we run this interval until status is 'COMPLETE'
|
|
160
|
+
const taskType = task.type;
|
|
161
|
+
if (!barInstances[taskType] && !mergeTasks[taskType]) {
|
|
162
|
+
// skip creation of lead-flows bar because we're combining lead-flows into the forms bar
|
|
163
|
+
barInstances[taskType] = multibar.create(100, 0, {
|
|
164
|
+
taskType: i18n(`${i18nKey}.${taskType}.label`),
|
|
165
|
+
});
|
|
166
|
+
} else if (mergeTasks[taskType]) {
|
|
167
|
+
// If its a lead-flow, merge status into the forms progress bar
|
|
168
|
+
const formsTask = taskResult.tasks.filter(
|
|
169
|
+
t => t.type === mergeTasks[taskType]
|
|
170
|
+
)[0];
|
|
171
|
+
const formsTaskStatus = formsTask.status;
|
|
172
|
+
const leadFlowsTaskStatus = task.status;
|
|
173
|
+
if (
|
|
174
|
+
formsTaskStatus !== 'COMPLETE' ||
|
|
175
|
+
leadFlowsTaskStatus !== 'COMPLETE'
|
|
176
|
+
) {
|
|
177
|
+
barInstances[mergeTasks[taskType]].increment(
|
|
178
|
+
Math.floor(Math.random() * 3),
|
|
179
|
+
{
|
|
180
|
+
taskType: i18n(`${i18nKey}.${mergeTasks[taskType]}.label`),
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (barInstances[taskType] && task.status === 'COMPLETE') {
|
|
186
|
+
barInstances[taskType].update(100, {
|
|
187
|
+
taskType: i18n(`${i18nKey}.${taskType}.label`),
|
|
188
|
+
});
|
|
189
|
+
} else if (barInstances[taskType] && task.status === 'PROCESSING') {
|
|
190
|
+
// Do not increment for tasks still in PENDING state
|
|
191
|
+
barInstances[taskType].increment(Math.floor(Math.random() * 3), {
|
|
192
|
+
// Randomly increment bar by 0 - 2 while sync is in progress. Sandboxes currently does not have an accurate measurement for progress.
|
|
193
|
+
taskType: i18n(`${i18nKey}.${taskType}.label`),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
clearInterval(pollInterval);
|
|
199
|
+
reject();
|
|
200
|
+
multibar.stop();
|
|
201
|
+
}
|
|
202
|
+
if (isTaskComplete(taskResult)) {
|
|
203
|
+
clearInterval(pollInterval);
|
|
204
|
+
resolve(taskResult);
|
|
205
|
+
multibar.stop();
|
|
206
|
+
}
|
|
207
|
+
}, ACTIVE_TASK_POLL_INTERVAL);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
getSandboxType,
|
|
213
|
+
getAccountName,
|
|
214
|
+
getAvailableSyncTypes,
|
|
215
|
+
sandboxCreatePersonalAccessKeyFlow,
|
|
216
|
+
pollSyncTaskStatus,
|
|
217
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.7-beta.1",
|
|
4
4
|
"description": "CLI for working with HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
"url": "https://github.com/HubSpot/hubspot-cms-tools"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@hubspot/cli-lib": "4.1.
|
|
12
|
-
"@hubspot/serverless-dev-runtime": "4.1.
|
|
11
|
+
"@hubspot/cli-lib": "4.1.7-beta.1",
|
|
12
|
+
"@hubspot/serverless-dev-runtime": "4.1.7-beta.1",
|
|
13
13
|
"archiver": "^5.3.0",
|
|
14
14
|
"chalk": "^4.1.2",
|
|
15
|
+
"cli-progress": "^3.11.2",
|
|
15
16
|
"express": "^4.17.1",
|
|
16
17
|
"findup-sync": "^4.0.0",
|
|
17
18
|
"fs-extra": "^8.1.0",
|
|
@@ -37,5 +38,5 @@
|
|
|
37
38
|
"publishConfig": {
|
|
38
39
|
"access": "public"
|
|
39
40
|
},
|
|
40
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "f97135fb4b7a43bbe9915191a70f38d509ba1b26"
|
|
41
42
|
}
|