@hubspot/cli 4.1.8-beta.0 → 4.1.8-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 +2 -2
- package/commands/sandbox/create.js +33 -146
- package/commands/sandbox/delete.js +4 -3
- package/commands/sandbox/sync.js +22 -148
- package/lib/CliProgressMultibarManager.js +66 -0
- package/lib/prompts/accountsPrompt.js +4 -2
- package/lib/prompts/sandboxesPrompt.js +41 -10
- package/lib/sandbox-create.js +337 -0
- package/lib/sandbox-sync.js +174 -0
- package/lib/sandboxes.js +145 -101
- package/package.json +4 -4
package/lib/sandboxes.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
const cliProgress = require('cli-progress');
|
|
2
1
|
const {
|
|
3
2
|
getConfig,
|
|
4
3
|
writeConfig,
|
|
5
4
|
updateAccountConfig,
|
|
5
|
+
getAccountId,
|
|
6
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
7
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
12
8
|
const { logger } = require('@hubspot/cli-lib/logger');
|
|
13
9
|
const {
|
|
@@ -15,36 +11,55 @@ const {
|
|
|
15
11
|
} = require('@hubspot/cli-lib/personalAccessKey');
|
|
16
12
|
const { EXIT_CODES } = require('./enums/exitCodes');
|
|
17
13
|
const { enterAccountNamePrompt } = require('./prompts/enterAccountNamePrompt');
|
|
14
|
+
const { fetchTaskStatus, fetchTypes } = require('@hubspot/cli-lib/sandboxes');
|
|
15
|
+
const { handleExit, handleKeypress } = require('@hubspot/cli-lib/lib/process');
|
|
16
|
+
const { accountNameExistsInConfig } = require('@hubspot/cli-lib/lib/config');
|
|
18
17
|
const {
|
|
19
18
|
personalAccessKeyPrompt,
|
|
20
19
|
} = require('./prompts/personalAccessKeyPrompt');
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
20
|
+
const CliProgressMultibarManager = require('./CliProgressMultibarManager');
|
|
21
|
+
|
|
22
|
+
const STANDARD_SANDBOX = 'standard';
|
|
23
|
+
const DEVELOPER_SANDBOX = 'developer';
|
|
24
|
+
|
|
25
|
+
const sandboxTypeMap = {
|
|
26
|
+
DEV: DEVELOPER_SANDBOX,
|
|
27
|
+
dev: DEVELOPER_SANDBOX,
|
|
28
|
+
DEVELOPER: DEVELOPER_SANDBOX,
|
|
29
|
+
developer: DEVELOPER_SANDBOX,
|
|
30
|
+
DEVELOPMENT: DEVELOPER_SANDBOX,
|
|
31
|
+
development: DEVELOPER_SANDBOX,
|
|
32
|
+
STANDARD: STANDARD_SANDBOX,
|
|
33
|
+
standard: STANDARD_SANDBOX,
|
|
34
|
+
};
|
|
27
35
|
|
|
28
|
-
const
|
|
36
|
+
const sandboxApiTypeMap = {
|
|
37
|
+
standard: 1,
|
|
38
|
+
developer: 2,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getSandboxTypeAsString = type =>
|
|
29
42
|
type === 'DEVELOPER' ? 'development' : 'standard';
|
|
30
43
|
|
|
31
44
|
function getAccountName(config) {
|
|
32
45
|
const isSandbox =
|
|
33
46
|
config.sandboxAccountType && config.sandboxAccountType !== null;
|
|
34
|
-
const sandboxName = `[${
|
|
47
|
+
const sandboxName = `[${getSandboxTypeAsString(
|
|
48
|
+
config.sandboxAccountType
|
|
49
|
+
)} sandbox] `;
|
|
35
50
|
return `${config.name} ${isSandbox ? sandboxName : ''}(${config.portalId})`;
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
function
|
|
53
|
+
function getHasSandboxesByType(parentAccountConfig, type) {
|
|
39
54
|
const config = getConfig();
|
|
40
|
-
const parentPortalId = parentAccountConfig.portalId;
|
|
55
|
+
const parentPortalId = getAccountId(parentAccountConfig.portalId);
|
|
41
56
|
for (const portal of config.portals) {
|
|
42
57
|
if (
|
|
43
58
|
(portal.parentAccountId !== null ||
|
|
44
59
|
portal.parentAccountId !== undefined) &&
|
|
45
60
|
portal.parentAccountId === parentPortalId &&
|
|
46
61
|
portal.sandboxAccountType &&
|
|
47
|
-
portal.sandboxAccountType ===
|
|
62
|
+
sandboxTypeMap[portal.sandboxAccountType] === type
|
|
48
63
|
) {
|
|
49
64
|
return true;
|
|
50
65
|
}
|
|
@@ -52,7 +67,7 @@ function getHasDevelopmentSandboxes(parentAccountConfig) {
|
|
|
52
67
|
return false;
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
function
|
|
70
|
+
function getSandboxLimit(error) {
|
|
56
71
|
// Error context should contain a limit property with a list of one number. That number is the current limit
|
|
57
72
|
const limit = error.context && error.context.limit && error.context.limit[0];
|
|
58
73
|
return limit ? parseInt(limit, 10) : 1; // Default to 1
|
|
@@ -60,31 +75,57 @@ function getDevSandboxLimit(error) {
|
|
|
60
75
|
|
|
61
76
|
// Fetches available sync types for a given sandbox portal
|
|
62
77
|
async function getAvailableSyncTypes(parentAccountConfig, config) {
|
|
63
|
-
const parentPortalId = parentAccountConfig.portalId;
|
|
64
|
-
const portalId = config.portalId;
|
|
78
|
+
const parentPortalId = getAccountId(parentAccountConfig.portalId);
|
|
79
|
+
const portalId = getAccountId(config.portalId);
|
|
65
80
|
const syncTypes = await fetchTypes(parentPortalId, portalId);
|
|
66
81
|
return syncTypes.map(t => ({ type: t.name }));
|
|
67
82
|
}
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
/**
|
|
85
|
+
* @param {String} env - Environment (QA/Prod)
|
|
86
|
+
* @param {Object} result - Sandbox instance returned from API
|
|
87
|
+
* @param {Boolean} force - Force flag to skip prompt
|
|
88
|
+
* @returns {String} validName saved into config
|
|
89
|
+
*/
|
|
90
|
+
const saveSandboxToConfig = async (env, result, force = false) => {
|
|
91
|
+
// const configData = { env, personalAccessKey: result.personalAccessKey };
|
|
92
|
+
// TODO: Temporary, remove
|
|
93
|
+
const configData = await personalAccessKeyPrompt({
|
|
94
|
+
env,
|
|
95
|
+
account: result.sandbox.sandboxHubId,
|
|
96
|
+
});
|
|
97
|
+
// End temporary section
|
|
71
98
|
const updatedConfig = await updateConfigWithPersonalAccessKey(configData);
|
|
72
|
-
|
|
73
99
|
if (!updatedConfig) {
|
|
74
|
-
|
|
100
|
+
throw new Error('Failed to update config with personal access key.');
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
let validName = updatedConfig.name;
|
|
78
|
-
|
|
79
104
|
if (!updatedConfig.name) {
|
|
80
|
-
const nameForConfig = name
|
|
105
|
+
const nameForConfig = result.sandbox.name
|
|
81
106
|
.toLowerCase()
|
|
82
107
|
.split(' ')
|
|
83
108
|
.join('-');
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
validName = nameForConfig;
|
|
110
|
+
const invalidAccountName = accountNameExistsInConfig(nameForConfig);
|
|
111
|
+
if (invalidAccountName) {
|
|
112
|
+
if (!force) {
|
|
113
|
+
logger.log(
|
|
114
|
+
i18n(
|
|
115
|
+
`cli.lib.prompts.enterAccountNamePrompt.errors.accountNameExists`,
|
|
116
|
+
{ name: nameForConfig }
|
|
117
|
+
)
|
|
118
|
+
);
|
|
119
|
+
const { name: promptName } = await enterAccountNamePrompt(
|
|
120
|
+
nameForConfig
|
|
121
|
+
);
|
|
122
|
+
validName = promptName;
|
|
123
|
+
} else {
|
|
124
|
+
// Basic invalid name handling when force flag is passed
|
|
125
|
+
validName = nameForConfig + `_${Date.now()}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
86
128
|
}
|
|
87
|
-
|
|
88
129
|
updateAccountConfig({
|
|
89
130
|
...updatedConfig,
|
|
90
131
|
environment: updatedConfig.env,
|
|
@@ -93,35 +134,8 @@ const sandboxCreatePersonalAccessKeyFlow = async (env, account, name) => {
|
|
|
93
134
|
});
|
|
94
135
|
writeConfig();
|
|
95
136
|
|
|
96
|
-
const setAsDefault = await setAsDefaultAccountPrompt(validName);
|
|
97
|
-
|
|
98
137
|
logger.log('');
|
|
99
|
-
|
|
100
|
-
logger.success(
|
|
101
|
-
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.setAsDefaultAccount`, {
|
|
102
|
-
accountName: validName,
|
|
103
|
-
})
|
|
104
|
-
);
|
|
105
|
-
} else {
|
|
106
|
-
const config = getConfig();
|
|
107
|
-
logger.info(
|
|
108
|
-
i18n(`cli.lib.prompts.setAsDefaultAccountPrompt.keepingCurrentDefault`, {
|
|
109
|
-
accountName: config.defaultPortal,
|
|
110
|
-
})
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
logger.success(
|
|
114
|
-
i18n('cli.commands.sandbox.subcommands.create.success.configFileUpdated', {
|
|
115
|
-
configFilename: DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME,
|
|
116
|
-
authMethod: PERSONAL_ACCESS_KEY_AUTH_METHOD.name,
|
|
117
|
-
account: validName,
|
|
118
|
-
})
|
|
119
|
-
);
|
|
120
|
-
uiFeatureHighlight([
|
|
121
|
-
'accountsUseCommand',
|
|
122
|
-
'accountOption',
|
|
123
|
-
'accountsListCommand',
|
|
124
|
-
]);
|
|
138
|
+
return validName;
|
|
125
139
|
};
|
|
126
140
|
|
|
127
141
|
const ACTIVE_TASK_POLL_INTERVAL = 1000;
|
|
@@ -133,26 +147,34 @@ const isTaskComplete = task => {
|
|
|
133
147
|
return task.status === 'COMPLETE';
|
|
134
148
|
};
|
|
135
149
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
const incrementBy = (value, multiplier = 3) => {
|
|
151
|
+
return Math.min(value + Math.floor(Math.random() * multiplier), 99);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {Number} accountId - Parent portal ID (needs sandbox scopes)
|
|
156
|
+
* @param {String} taksId - Task ID to poll
|
|
157
|
+
* @param {String} syncStatusUrl - Link to UI to check polling status
|
|
158
|
+
* @param {Boolean} allowEarlyTermination - Option to allow a keypress to terminate early
|
|
159
|
+
* @returns {Promise} Interval runs until sync task status is equal to 'COMPLETE'
|
|
160
|
+
*/
|
|
161
|
+
function pollSyncTaskStatus(
|
|
162
|
+
accountId,
|
|
163
|
+
taskId,
|
|
164
|
+
syncStatusUrl,
|
|
165
|
+
allowEarlyTermination = true
|
|
166
|
+
) {
|
|
167
|
+
const i18nKey = 'cli.lib.sandbox.sync.types';
|
|
168
|
+
const progressBar = CliProgressMultibarManager.init();
|
|
147
169
|
const mergeTasks = {
|
|
148
170
|
'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.
|
|
149
171
|
};
|
|
150
|
-
|
|
172
|
+
let progressCounter = {};
|
|
151
173
|
let pollInterval;
|
|
152
174
|
// Handle manual exit for return key and ctrl+c
|
|
153
175
|
const onTerminate = () => {
|
|
154
176
|
clearInterval(pollInterval);
|
|
155
|
-
|
|
177
|
+
progressBar.stop();
|
|
156
178
|
logger.log('');
|
|
157
179
|
logger.log('Exiting, sync will continue in the background.');
|
|
158
180
|
logger.log('');
|
|
@@ -163,16 +185,18 @@ function pollSyncTaskStatus(accountId, taskId, syncStatusUrl) {
|
|
|
163
185
|
);
|
|
164
186
|
process.exit(EXIT_CODES.SUCCESS);
|
|
165
187
|
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
188
|
+
if (allowEarlyTermination) {
|
|
189
|
+
handleExit(onTerminate);
|
|
190
|
+
handleKeypress(key => {
|
|
191
|
+
if (
|
|
192
|
+
(key && key.ctrl && key.name == 'c') ||
|
|
193
|
+
key.name === 'enter' ||
|
|
194
|
+
key.name === 'return'
|
|
195
|
+
) {
|
|
196
|
+
onTerminate();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
176
200
|
return new Promise((resolve, reject) => {
|
|
177
201
|
pollInterval = setInterval(async () => {
|
|
178
202
|
const taskResult = await fetchTaskStatus(accountId, taskId).catch(reject);
|
|
@@ -181,13 +205,17 @@ function pollSyncTaskStatus(accountId, taskId, syncStatusUrl) {
|
|
|
181
205
|
for (const task of taskResult.tasks) {
|
|
182
206
|
// For each sync task, show a progress bar and increment bar each time we run this interval until status is 'COMPLETE'
|
|
183
207
|
const taskType = task.type;
|
|
184
|
-
if (!
|
|
185
|
-
// skip creation of lead-flows bar because we're combining lead-flows into the forms bar
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
if (!progressBar.get(taskType) && !mergeTasks[taskType]) {
|
|
209
|
+
// skip creation of lead-flows bar because we're combining lead-flows into the forms bar, otherwise create a bar instance for the type
|
|
210
|
+
progressCounter[taskType] = 0;
|
|
211
|
+
progressBar.create(taskType, 100, 0, {
|
|
212
|
+
label: i18n(`${i18nKey}.${taskType}.label`),
|
|
188
213
|
});
|
|
189
214
|
} else if (mergeTasks[taskType]) {
|
|
190
|
-
//
|
|
215
|
+
// It's a lead-flow here, merge status into the forms progress bar
|
|
216
|
+
if (!progressCounter[mergeTasks[taskType]]) {
|
|
217
|
+
progressCounter[mergeTasks[taskType]] = 0;
|
|
218
|
+
}
|
|
191
219
|
const formsTask = taskResult.tasks.filter(
|
|
192
220
|
t => t.type === mergeTasks[taskType]
|
|
193
221
|
)[0];
|
|
@@ -197,46 +225,62 @@ function pollSyncTaskStatus(accountId, taskId, syncStatusUrl) {
|
|
|
197
225
|
formsTaskStatus !== 'COMPLETE' ||
|
|
198
226
|
leadFlowsTaskStatus !== 'COMPLETE'
|
|
199
227
|
) {
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
// Randomly increment bar while sync is in progress. Sandboxes currently does not have an accurate measurement for progress.
|
|
229
|
+
progressCounter[mergeTasks[taskType]] = incrementBy(
|
|
230
|
+
progressCounter[mergeTasks[taskType]]
|
|
231
|
+
);
|
|
232
|
+
progressBar.update(
|
|
233
|
+
mergeTasks[taskType],
|
|
234
|
+
progressCounter[mergeTasks[taskType]],
|
|
202
235
|
{
|
|
203
|
-
|
|
236
|
+
label: i18n(`${i18nKey}.${mergeTasks[taskType]}.label`),
|
|
204
237
|
}
|
|
205
238
|
);
|
|
206
239
|
}
|
|
207
240
|
}
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
241
|
+
if (progressBar.get(taskType) && task.status === 'COMPLETE') {
|
|
242
|
+
progressBar.update(taskType, 100, {
|
|
243
|
+
label: i18n(`${i18nKey}.${taskType}.label`),
|
|
211
244
|
});
|
|
212
|
-
} else if (
|
|
213
|
-
// Do not
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
245
|
+
} else if (
|
|
246
|
+
// Do not start incrementing for tasks still in PENDING state
|
|
247
|
+
progressBar.get(taskType) &&
|
|
248
|
+
task.status === 'PROCESSING'
|
|
249
|
+
) {
|
|
250
|
+
// Randomly increment bar while sync is in progress. Sandboxes currently does not have an accurate measurement for progress.
|
|
251
|
+
progressCounter[taskType] = incrementBy(
|
|
252
|
+
progressCounter[taskType],
|
|
253
|
+
taskType === 'object-records' ? 2 : 3 // slower progress for object-records, sync can take up to a few minutes
|
|
254
|
+
);
|
|
255
|
+
progressBar.update(taskType, progressCounter[taskType], {
|
|
256
|
+
label: i18n(`${i18nKey}.${taskType}.label`),
|
|
217
257
|
});
|
|
218
258
|
}
|
|
219
259
|
}
|
|
220
260
|
} else {
|
|
221
261
|
clearInterval(pollInterval);
|
|
222
262
|
reject();
|
|
223
|
-
|
|
263
|
+
progressBar.stop();
|
|
224
264
|
}
|
|
225
265
|
if (isTaskComplete(taskResult)) {
|
|
226
266
|
clearInterval(pollInterval);
|
|
227
267
|
resolve(taskResult);
|
|
228
|
-
|
|
268
|
+
progressBar.stop();
|
|
229
269
|
}
|
|
230
270
|
}, ACTIVE_TASK_POLL_INTERVAL);
|
|
231
271
|
});
|
|
232
272
|
}
|
|
233
273
|
|
|
234
274
|
module.exports = {
|
|
235
|
-
|
|
275
|
+
STANDARD_SANDBOX,
|
|
276
|
+
DEVELOPER_SANDBOX,
|
|
277
|
+
sandboxTypeMap,
|
|
278
|
+
sandboxApiTypeMap,
|
|
279
|
+
getSandboxTypeAsString,
|
|
236
280
|
getAccountName,
|
|
237
|
-
|
|
238
|
-
|
|
281
|
+
saveSandboxToConfig,
|
|
282
|
+
getHasSandboxesByType,
|
|
283
|
+
getSandboxLimit,
|
|
239
284
|
getAvailableSyncTypes,
|
|
240
|
-
sandboxCreatePersonalAccessKeyFlow,
|
|
241
285
|
pollSyncTaskStatus,
|
|
242
286
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/cli",
|
|
3
|
-
"version": "4.1.8-beta.
|
|
3
|
+
"version": "4.1.8-beta.1",
|
|
4
4
|
"description": "CLI for working with HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/HubSpot/hubspot-cms-tools"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@hubspot/cli-lib": "4.1.8-beta.
|
|
12
|
-
"@hubspot/serverless-dev-runtime": "4.1.8-beta.
|
|
11
|
+
"@hubspot/cli-lib": "4.1.8-beta.1",
|
|
12
|
+
"@hubspot/serverless-dev-runtime": "4.1.8-beta.1",
|
|
13
13
|
"archiver": "^5.3.0",
|
|
14
14
|
"chalk": "^4.1.2",
|
|
15
15
|
"cli-progress": "^3.11.2",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "8aafada9270602e912d89fc6a513816f5f7232bb"
|
|
42
42
|
}
|