@hubspot/cli 4.1.7 → 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/accounts/remove.js +84 -0
- package/commands/accounts.js +2 -0
- package/commands/functions/deploy.js +0 -1
- package/commands/functions/list.js +1 -1
- package/commands/list.js +1 -5
- package/commands/sandbox/create.js +34 -120
- package/commands/sandbox/delete.js +28 -11
- package/commands/sandbox/sync.js +22 -148
- package/lib/CliProgressMultibarManager.js +66 -0
- package/lib/prompts/accountsPrompt.js +6 -4
- package/lib/prompts/projectsLogsPrompt.js +1 -1
- 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 +164 -95
- package/package.json +4 -4
package/commands/sandbox/sync.js
CHANGED
|
@@ -7,12 +7,8 @@ const {
|
|
|
7
7
|
} = require('../../lib/commonOpts');
|
|
8
8
|
const { trackCommandUsage } = require('../../lib/usageTracking');
|
|
9
9
|
const { logger } = require('@hubspot/cli-lib/logger');
|
|
10
|
-
const Spinnies = require('spinnies');
|
|
11
|
-
const { initiateSync } = require('@hubspot/cli-lib/sandboxes');
|
|
12
10
|
const { loadAndValidateOptions } = require('../../lib/validation');
|
|
13
11
|
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
12
|
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
17
13
|
const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
|
|
18
14
|
const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
@@ -20,13 +16,12 @@ const { promptUser } = require('../../lib/prompts/promptUtils');
|
|
|
20
16
|
const { uiLine } = require('../../lib/ui');
|
|
21
17
|
const {
|
|
22
18
|
getAccountName,
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
sandboxTypeMap,
|
|
20
|
+
DEVELOPER_SANDBOX,
|
|
21
|
+
STANDARD_SANDBOX,
|
|
25
22
|
} = require('../../lib/sandboxes');
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
isSpecifiedError,
|
|
29
|
-
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
23
|
+
const { syncSandbox } = require('../../lib/sandbox-sync');
|
|
24
|
+
const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
|
|
30
25
|
|
|
31
26
|
const i18nKey = 'cli.commands.sandbox.subcommands.sync';
|
|
32
27
|
|
|
@@ -39,9 +34,7 @@ exports.handler = async options => {
|
|
|
39
34
|
const { force } = options; // For scripting purposes
|
|
40
35
|
const accountId = getAccountId(options);
|
|
41
36
|
const accountConfig = getAccountConfig(accountId);
|
|
42
|
-
const
|
|
43
|
-
succeedColor: 'white',
|
|
44
|
-
});
|
|
37
|
+
const env = getValidEnv(getEnv(accountId));
|
|
45
38
|
|
|
46
39
|
trackCommandUsage('sandbox-sync', null, accountId);
|
|
47
40
|
|
|
@@ -51,10 +44,8 @@ exports.handler = async options => {
|
|
|
51
44
|
accountConfig.sandboxAccountType === undefined ||
|
|
52
45
|
accountConfig.sandboxAccountType === null
|
|
53
46
|
) {
|
|
54
|
-
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
55
|
-
|
|
56
47
|
logger.error(i18n(`${i18nKey}.failure.notSandbox`));
|
|
57
|
-
|
|
48
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
58
49
|
process.exit(EXIT_CODES.ERROR);
|
|
59
50
|
}
|
|
60
51
|
|
|
@@ -67,12 +58,15 @@ exports.handler = async options => {
|
|
|
67
58
|
sandboxName: getAccountName(accountConfig),
|
|
68
59
|
})
|
|
69
60
|
);
|
|
61
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
70
62
|
process.exit(EXIT_CODES.ERROR);
|
|
71
63
|
}
|
|
72
64
|
|
|
73
65
|
const parentAccountConfig = getAccountConfig(parentAccountId);
|
|
74
|
-
const isDevelopmentSandbox =
|
|
75
|
-
|
|
66
|
+
const isDevelopmentSandbox =
|
|
67
|
+
sandboxTypeMap[accountConfig.sandboxAccountType] === DEVELOPER_SANDBOX;
|
|
68
|
+
const isStandardSandbox =
|
|
69
|
+
sandboxTypeMap[accountConfig.sandboxAccountType] === STANDARD_SANDBOX;
|
|
76
70
|
|
|
77
71
|
if (isDevelopmentSandbox) {
|
|
78
72
|
logger.log(i18n(`${i18nKey}.info.developmentSandbox`));
|
|
@@ -100,12 +94,13 @@ exports.handler = async options => {
|
|
|
100
94
|
},
|
|
101
95
|
]);
|
|
102
96
|
if (!confirmed) {
|
|
97
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
103
98
|
process.exit(EXIT_CODES.SUCCESS);
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
101
|
} else if (isStandardSandbox) {
|
|
107
102
|
const standardSyncUrl = `${getHubSpotWebsiteOrigin(
|
|
108
|
-
|
|
103
|
+
env
|
|
109
104
|
)}/sandboxes-developer/${parentAccountId}/sync?step=select_sync_path&id=${parentAccountId}_${accountId}`;
|
|
110
105
|
|
|
111
106
|
logger.log(
|
|
@@ -137,147 +132,26 @@ exports.handler = async options => {
|
|
|
137
132
|
},
|
|
138
133
|
]);
|
|
139
134
|
if (!confirmed) {
|
|
135
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
140
136
|
process.exit(EXIT_CODES.SUCCESS);
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
139
|
} else {
|
|
144
140
|
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
141
|
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
142
|
process.exit(EXIT_CODES.ERROR);
|
|
241
143
|
}
|
|
242
144
|
|
|
243
145
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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`),
|
|
146
|
+
await syncSandbox({
|
|
147
|
+
accountConfig,
|
|
148
|
+
parentAccountConfig,
|
|
149
|
+
env,
|
|
150
|
+
allowEarlyTermination: true,
|
|
259
151
|
});
|
|
260
|
-
logger.log('');
|
|
261
|
-
logger.log(
|
|
262
|
-
i18n(`${i18nKey}.info.syncStatus`, {
|
|
263
|
-
url: syncStatusUrl,
|
|
264
|
-
})
|
|
265
|
-
);
|
|
266
|
-
|
|
267
152
|
process.exit(EXIT_CODES.SUCCESS);
|
|
268
|
-
} catch (
|
|
269
|
-
|
|
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
|
-
|
|
153
|
+
} catch (error) {
|
|
154
|
+
trackCommandUsage('sandbox-sync', { successful: false }, accountId);
|
|
281
155
|
process.exit(EXIT_CODES.ERROR);
|
|
282
156
|
}
|
|
283
157
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// https://github.com/npkgz/cli-progress/blob/master/README.md
|
|
2
|
+
const ProgressMultibarManager = require('cli-progress');
|
|
3
|
+
|
|
4
|
+
class CliProgressMultibarManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.multibar = null;
|
|
7
|
+
this.barInstances = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
init(options) {
|
|
11
|
+
if (!this.multibar) {
|
|
12
|
+
this.multibar = new ProgressMultibarManager.MultiBar(
|
|
13
|
+
{
|
|
14
|
+
hideCursor: true,
|
|
15
|
+
format: '[{bar}] {percentage}% | {label}',
|
|
16
|
+
gracefulExit: true,
|
|
17
|
+
...options,
|
|
18
|
+
},
|
|
19
|
+
ProgressMultibarManager.Presets.rect
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
get: this.get.bind(this),
|
|
25
|
+
create: this.create.bind(this),
|
|
26
|
+
update: this.update.bind(this),
|
|
27
|
+
increment: this.increment.bind(this),
|
|
28
|
+
remove: this.multibar.remove.bind(this.multibar),
|
|
29
|
+
stop: this.multibar.stop.bind(this.multibar),
|
|
30
|
+
log: this.multibar.log.bind(this.multibar),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get(barName) {
|
|
35
|
+
return this.barInstances[barName];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
create(barName, total = 100, startValue = 0, options = {}) {
|
|
39
|
+
if (!this.multibar) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!this.barInstances[barName]) {
|
|
43
|
+
this.barInstances[barName] = this.multibar.create(
|
|
44
|
+
total,
|
|
45
|
+
startValue,
|
|
46
|
+
options
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
update(barName, value, options = {}) {
|
|
52
|
+
const barInstance = this.barInstances[barName];
|
|
53
|
+
if (barInstance) {
|
|
54
|
+
barInstance.update(value, options);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
increment(barName, value, options = {}) {
|
|
59
|
+
const barInstance = this.barInstances[barName];
|
|
60
|
+
if (barInstance) {
|
|
61
|
+
barInstance.increment(value, options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = new CliProgressMultibarManager();
|
|
@@ -1,12 +1,14 @@
|
|
|
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 { getSandboxTypeAsString } = require('../sandboxes');
|
|
5
5
|
|
|
6
6
|
const mapAccountChoices = portals =>
|
|
7
7
|
portals.map(p => {
|
|
8
8
|
const isSandbox = p.sandboxAccountType && p.sandboxAccountType !== null;
|
|
9
|
-
const sandboxName = `[${
|
|
9
|
+
const sandboxName = `[${getSandboxTypeAsString(
|
|
10
|
+
p.sandboxAccountType
|
|
11
|
+
)} sandbox] `;
|
|
10
12
|
return {
|
|
11
13
|
name: `${p.name} ${isSandbox ? sandboxName : ''}(${p.portalId})`,
|
|
12
14
|
value: p.name || p.portalId,
|
|
@@ -15,14 +17,14 @@ const mapAccountChoices = portals =>
|
|
|
15
17
|
|
|
16
18
|
const i18nKey = 'cli.commands.accounts.subcommands.use';
|
|
17
19
|
|
|
18
|
-
const selectAccountFromConfig = async config => {
|
|
20
|
+
const selectAccountFromConfig = async (config, prompt) => {
|
|
19
21
|
const { default: selectedDefault } = await promptUser([
|
|
20
22
|
{
|
|
21
23
|
type: 'list',
|
|
22
24
|
look: false,
|
|
23
25
|
name: 'default',
|
|
24
26
|
pageSize: 20,
|
|
25
|
-
message: i18n(`${i18nKey}.promptMessage`),
|
|
27
|
+
message: prompt || i18n(`${i18nKey}.promptMessage`),
|
|
26
28
|
choices: mapAccountChoices(config.portals),
|
|
27
29
|
default: config.defaultPortal,
|
|
28
30
|
},
|
|
@@ -66,7 +66,7 @@ const projectLogsPrompt = (accountId, promptOptions = {}) => {
|
|
|
66
66
|
|
|
67
67
|
if (deployedBuild && deployedBuild.subbuildStatuses) {
|
|
68
68
|
return deployedBuild.subbuildStatuses
|
|
69
|
-
.filter(subbuild => subbuild.buildType === '
|
|
69
|
+
.filter(subbuild => subbuild.buildType === 'PRIVATE_APP')
|
|
70
70
|
.map(subbuild => ({
|
|
71
71
|
name: subbuild.buildName,
|
|
72
72
|
value: subbuild.buildName,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { promptUser } = require('./promptUtils');
|
|
2
2
|
const { i18n } = require('@hubspot/cli-lib/lib/lang');
|
|
3
|
-
const {
|
|
3
|
+
const { getSandboxTypeAsString } = require('../sandboxes');
|
|
4
|
+
const { accountNameExistsInConfig } = require('@hubspot/cli-lib/lib/config');
|
|
4
5
|
|
|
5
6
|
const i18nKey = 'cli.lib.prompts.sandboxesPrompt';
|
|
6
7
|
|
|
@@ -8,7 +9,9 @@ const mapSandboxAccountChoices = portals =>
|
|
|
8
9
|
portals
|
|
9
10
|
.filter(p => p.sandboxAccountType && p.sandboxAccountType !== null)
|
|
10
11
|
.map(p => {
|
|
11
|
-
const sandboxName = `[${
|
|
12
|
+
const sandboxName = `[${getSandboxTypeAsString(
|
|
13
|
+
p.sandboxAccountType
|
|
14
|
+
)} sandbox] `;
|
|
12
15
|
return {
|
|
13
16
|
name: `${p.name} ${sandboxName}(${p.portalId})`,
|
|
14
17
|
value: p.name || p.portalId,
|
|
@@ -27,22 +30,50 @@ const mapNonSandboxAccountChoices = portals =>
|
|
|
27
30
|
};
|
|
28
31
|
});
|
|
29
32
|
|
|
30
|
-
const
|
|
33
|
+
const sandboxNamePrompt = () => {
|
|
31
34
|
return promptUser([
|
|
32
35
|
{
|
|
33
36
|
name: 'name',
|
|
34
|
-
message: i18n(`${i18nKey}.
|
|
37
|
+
message: i18n(`${i18nKey}.name.message`),
|
|
35
38
|
validate(val) {
|
|
36
39
|
if (typeof val !== 'string') {
|
|
37
|
-
return i18n(`${i18nKey}.errors.invalidName`);
|
|
40
|
+
return i18n(`${i18nKey}.name.errors.invalidName`);
|
|
41
|
+
} else if (!val.length) {
|
|
42
|
+
return i18n(`${i18nKey}.name.errors.nameRequired`);
|
|
38
43
|
}
|
|
39
|
-
return
|
|
44
|
+
return accountNameExistsInConfig(val)
|
|
45
|
+
? i18n(`${i18nKey}.name.errors.accountNameExists`, { name: val })
|
|
46
|
+
: true;
|
|
40
47
|
},
|
|
41
48
|
default: 'New sandbox',
|
|
42
49
|
},
|
|
43
50
|
]);
|
|
44
51
|
};
|
|
45
52
|
|
|
53
|
+
const sandboxTypeChoices = [
|
|
54
|
+
{
|
|
55
|
+
name: i18n(`${i18nKey}.type.developer`),
|
|
56
|
+
value: 'DEVELOPER',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: i18n(`${i18nKey}.type.standard`),
|
|
60
|
+
value: 'STANDARD',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const sandboxTypePrompt = () => {
|
|
65
|
+
return promptUser([
|
|
66
|
+
{
|
|
67
|
+
name: 'type',
|
|
68
|
+
message: i18n(`${i18nKey}.type.message`),
|
|
69
|
+
type: 'list',
|
|
70
|
+
look: false,
|
|
71
|
+
choices: sandboxTypeChoices,
|
|
72
|
+
default: 'DEVELOPER',
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
};
|
|
76
|
+
|
|
46
77
|
const deleteSandboxPrompt = (config, promptParentAccount = false) => {
|
|
47
78
|
const choices = promptParentAccount
|
|
48
79
|
? mapNonSandboxAccountChoices(config.portals)
|
|
@@ -55,8 +86,8 @@ const deleteSandboxPrompt = (config, promptParentAccount = false) => {
|
|
|
55
86
|
name: 'account',
|
|
56
87
|
message: i18n(
|
|
57
88
|
promptParentAccount
|
|
58
|
-
? `${i18nKey}.selectParentAccountName`
|
|
59
|
-
: `${i18nKey}.selectAccountName`
|
|
89
|
+
? `${i18nKey}.name.selectParentAccountName`
|
|
90
|
+
: `${i18nKey}.name.selectAccountName`
|
|
60
91
|
),
|
|
61
92
|
type: 'list',
|
|
62
93
|
look: false,
|
|
@@ -68,7 +99,7 @@ const deleteSandboxPrompt = (config, promptParentAccount = false) => {
|
|
|
68
99
|
};
|
|
69
100
|
|
|
70
101
|
module.exports = {
|
|
71
|
-
|
|
102
|
+
sandboxNamePrompt,
|
|
103
|
+
sandboxTypePrompt,
|
|
72
104
|
deleteSandboxPrompt,
|
|
73
|
-
getSandboxType,
|
|
74
105
|
};
|