@hubspot/cli 4.1.8-beta.3 → 4.1.8-beta.5
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/project/dev.js +71 -64
- package/commands/project/upload.js +28 -0
- package/lang/en.lyaml +26 -24
- package/lib/DevServerManager.js +93 -0
- package/lib/LocalDevManager.js +133 -66
- package/lib/SpinniesManager.js +14 -5
- package/lib/projects.js +12 -18
- package/lib/prompts/projectDevTargetAccountPrompt.js +45 -71
- package/lib/sandboxes.js +7 -4
- package/package.json +7 -5
package/commands/project/dev.js
CHANGED
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
LocalDevManager,
|
|
30
30
|
UPLOAD_PERMISSIONS,
|
|
31
31
|
} = require('../../lib/LocalDevManager');
|
|
32
|
+
const { isSandbox } = require('../../lib/sandboxes');
|
|
32
33
|
const { getAccountConfig, getEnv } = require('@hubspot/cli-lib');
|
|
33
34
|
const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
|
|
34
35
|
const {
|
|
@@ -37,17 +38,23 @@ const {
|
|
|
37
38
|
getAvailableSyncTypes,
|
|
38
39
|
} = require('../../lib/sandboxes');
|
|
39
40
|
const { getValidEnv } = require('@hubspot/cli-lib/lib/environment');
|
|
40
|
-
const {
|
|
41
|
+
const { ERROR_TYPES } = require('@hubspot/cli-lib/lib/constants');
|
|
42
|
+
const {
|
|
43
|
+
logErrorInstance,
|
|
44
|
+
logApiErrorInstance,
|
|
45
|
+
ApiErrorContext,
|
|
46
|
+
} = require('@hubspot/cli-lib/errorHandlers');
|
|
41
47
|
const { buildSandbox } = require('../../lib/sandbox-create');
|
|
42
48
|
const { syncSandbox } = require('../../lib/sandbox-sync');
|
|
43
49
|
const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
44
50
|
const {
|
|
45
51
|
isMissingScopeError,
|
|
52
|
+
isSpecifiedError,
|
|
46
53
|
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
47
54
|
|
|
48
55
|
const i18nKey = 'cli.commands.project.subcommands.dev';
|
|
49
56
|
|
|
50
|
-
exports.command = 'dev [--account] [--
|
|
57
|
+
exports.command = 'dev [--account] [--port]';
|
|
51
58
|
exports.describe = null; //i18n(`${i18nKey}.describe`);
|
|
52
59
|
|
|
53
60
|
exports.handler = async options => {
|
|
@@ -60,13 +67,7 @@ exports.handler = async options => {
|
|
|
60
67
|
|
|
61
68
|
const { projectConfig, projectDir } = await getProjectConfig();
|
|
62
69
|
|
|
63
|
-
logger.log(i18n(`${i18nKey}.logs.
|
|
64
|
-
uiLine();
|
|
65
|
-
logger.log(i18n(`${i18nKey}.logs.introBody1`));
|
|
66
|
-
logger.log();
|
|
67
|
-
logger.log(i18n(`${i18nKey}.logs.introBody2`));
|
|
68
|
-
uiLine();
|
|
69
|
-
logger.log();
|
|
70
|
+
logger.log(i18n(`${i18nKey}.logs.betaMessage`));
|
|
70
71
|
|
|
71
72
|
if (!projectConfig) {
|
|
72
73
|
logger.error(i18n(`${i18nKey}.errors.noProjectConfig`));
|
|
@@ -74,47 +75,32 @@ exports.handler = async options => {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const accounts = getConfigAccounts();
|
|
77
|
-
let targetAccountId = options.accountId;
|
|
78
|
+
let targetAccountId = options.account ? accountId : null;
|
|
78
79
|
let createNewSandbox = false;
|
|
79
|
-
|
|
80
|
+
const defaultAccountIsSandbox = isSandbox(accountConfig);
|
|
81
|
+
|
|
82
|
+
if (!targetAccountId && defaultAccountIsSandbox) {
|
|
83
|
+
targetAccountId = accountId;
|
|
84
|
+
}
|
|
80
85
|
|
|
81
86
|
if (!targetAccountId) {
|
|
87
|
+
//logger.log(i18n(`${i18nKey}.logs.learnMoreLink`));
|
|
88
|
+
logger.log();
|
|
89
|
+
uiLine();
|
|
90
|
+
logger.warn(i18n(`${i18nKey}.logs.nonSandboxWarning`));
|
|
91
|
+
uiLine();
|
|
92
|
+
logger.log();
|
|
93
|
+
|
|
82
94
|
const {
|
|
83
95
|
targetAccountId: promptTargetAccountId,
|
|
84
|
-
chooseNonSandbox: promptChooseNonSandbox,
|
|
85
96
|
createNewSandbox: promptCreateNewSandbox,
|
|
86
|
-
} = await selectTargetAccountPrompt(accounts, accountConfig
|
|
97
|
+
} = await selectTargetAccountPrompt(accounts, accountConfig);
|
|
87
98
|
|
|
88
99
|
targetAccountId = promptTargetAccountId;
|
|
89
|
-
chooseNonSandbox = promptChooseNonSandbox;
|
|
90
100
|
createNewSandbox = promptCreateNewSandbox;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// Show a warning if the user chooses a non-sandbox account (false)
|
|
96
|
-
let shouldTargetNonSandboxAccount;
|
|
97
|
-
if (chooseNonSandbox) {
|
|
98
|
-
uiLine();
|
|
99
|
-
logger.warn(i18n(`${i18nKey}.logs.prodAccountWarning`));
|
|
100
|
-
uiLine();
|
|
101
|
-
logger.log();
|
|
102
|
-
|
|
103
|
-
shouldTargetNonSandboxAccount = await confirmPrompt(
|
|
104
|
-
i18n(`${i18nKey}.prompt.targetNonSandbox`)
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
if (shouldTargetNonSandboxAccount) {
|
|
108
|
-
const {
|
|
109
|
-
targetAccountId: promptNonSandboxTargetAccountId,
|
|
110
|
-
} = await selectTargetAccountPrompt(accounts, accountConfig, true);
|
|
111
|
-
|
|
112
|
-
targetAccountId = promptNonSandboxTargetAccountId;
|
|
113
|
-
logger.log();
|
|
114
|
-
} else {
|
|
115
|
-
process.exit(EXIT_CODES.SUCCESS);
|
|
116
|
-
}
|
|
117
|
-
} else if (createNewSandbox) {
|
|
103
|
+
if (createNewSandbox) {
|
|
118
104
|
try {
|
|
119
105
|
await validateSandboxUsageLimits(accountConfig, DEVELOPER_SANDBOX, env);
|
|
120
106
|
} catch (err) {
|
|
@@ -179,7 +165,8 @@ exports.handler = async options => {
|
|
|
179
165
|
}
|
|
180
166
|
);
|
|
181
167
|
|
|
182
|
-
const isNonSandboxAccount =
|
|
168
|
+
const isNonSandboxAccount =
|
|
169
|
+
!defaultAccountIsSandbox && targetAccountId === accountId;
|
|
183
170
|
|
|
184
171
|
let uploadPermission = isNonSandboxAccount
|
|
185
172
|
? UPLOAD_PERMISSIONS.manual
|
|
@@ -198,21 +185,26 @@ exports.handler = async options => {
|
|
|
198
185
|
const spinnies = SpinniesManager.init();
|
|
199
186
|
|
|
200
187
|
if (!projectExists) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
188
|
+
// Create the project without prompting if this is a newly created sandbox
|
|
189
|
+
let shouldCreateProject = createNewSandbox;
|
|
190
|
+
|
|
191
|
+
if (!shouldCreateProject) {
|
|
192
|
+
uiLine();
|
|
193
|
+
logger.warn(
|
|
194
|
+
i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
|
|
195
|
+
accountIdentifier: uiAccountDescription(targetAccountId),
|
|
196
|
+
projectName: projectConfig.name,
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
uiLine();
|
|
209
200
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
201
|
+
shouldCreateProject = await confirmPrompt(
|
|
202
|
+
i18n(`${i18nKey}.prompt.createProject`, {
|
|
203
|
+
accountIdentifier: uiAccountDescription(targetAccountId),
|
|
204
|
+
projectName: projectConfig.name,
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
}
|
|
216
208
|
|
|
217
209
|
if (shouldCreateProject) {
|
|
218
210
|
try {
|
|
@@ -228,9 +220,10 @@ exports.handler = async options => {
|
|
|
228
220
|
accountIdentifier: uiAccountDescription(targetAccountId),
|
|
229
221
|
projectName: projectConfig.name,
|
|
230
222
|
}),
|
|
223
|
+
succeedColor: 'white',
|
|
231
224
|
});
|
|
232
225
|
} catch (err) {
|
|
233
|
-
logger.log(i18n(`${i18nKey}.
|
|
226
|
+
logger.log(i18n(`${i18nKey}.status.failedToCreateProject`));
|
|
234
227
|
process.exit(EXIT_CODES.ERROR);
|
|
235
228
|
}
|
|
236
229
|
} else {
|
|
@@ -260,10 +253,24 @@ exports.handler = async options => {
|
|
|
260
253
|
);
|
|
261
254
|
}
|
|
262
255
|
|
|
263
|
-
if (result &&
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
256
|
+
if (result && result.error) {
|
|
257
|
+
if (
|
|
258
|
+
isSpecifiedError(result.error, {
|
|
259
|
+
subCategory: ERROR_TYPES.PROJECT_LOCKED,
|
|
260
|
+
})
|
|
261
|
+
) {
|
|
262
|
+
logger.log();
|
|
263
|
+
logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
|
|
264
|
+
logger.log();
|
|
265
|
+
} else {
|
|
266
|
+
logApiErrorInstance(
|
|
267
|
+
result.error,
|
|
268
|
+
new ApiErrorContext({
|
|
269
|
+
accountId,
|
|
270
|
+
projectName: projectConfig.name,
|
|
271
|
+
})
|
|
272
|
+
);
|
|
273
|
+
}
|
|
267
274
|
process.exit(EXIT_CODES.ERROR);
|
|
268
275
|
} else {
|
|
269
276
|
spinnies.remove('devModeSetup');
|
|
@@ -271,11 +278,11 @@ exports.handler = async options => {
|
|
|
271
278
|
|
|
272
279
|
const LocalDev = new LocalDevManager({
|
|
273
280
|
debug: options.debug,
|
|
274
|
-
mockServers: options.mockServers,
|
|
275
281
|
projectConfig,
|
|
276
282
|
projectDir,
|
|
277
283
|
targetAccountId,
|
|
278
284
|
uploadPermission,
|
|
285
|
+
port: options.port,
|
|
279
286
|
});
|
|
280
287
|
|
|
281
288
|
await LocalDev.start();
|
|
@@ -289,11 +296,11 @@ exports.builder = yargs => {
|
|
|
289
296
|
addUseEnvironmentOptions(yargs, true);
|
|
290
297
|
addTestingOptions(yargs, true);
|
|
291
298
|
|
|
292
|
-
yargs.option('
|
|
293
|
-
describe:
|
|
294
|
-
type: '
|
|
295
|
-
default: false,
|
|
299
|
+
yargs.option('port', {
|
|
300
|
+
describe: i18n(`${i18nKey}.options.port.describe`),
|
|
301
|
+
type: 'number',
|
|
296
302
|
});
|
|
303
|
+
|
|
297
304
|
yargs.example([['$0 project dev', i18n(`${i18nKey}.examples.default`)]]);
|
|
298
305
|
|
|
299
306
|
return yargs;
|
|
@@ -19,6 +19,14 @@ const {
|
|
|
19
19
|
} = require('../../lib/projects');
|
|
20
20
|
const { i18n } = require('../../lib/lang');
|
|
21
21
|
const { getAccountConfig } = require('@hubspot/cli-lib');
|
|
22
|
+
const { ERROR_TYPES } = require('@hubspot/cli-lib/lib/constants');
|
|
23
|
+
const {
|
|
24
|
+
isSpecifiedError,
|
|
25
|
+
} = require('@hubspot/cli-lib/errorHandlers/apiErrors');
|
|
26
|
+
const {
|
|
27
|
+
logApiErrorInstance,
|
|
28
|
+
ApiErrorContext,
|
|
29
|
+
} = require('@hubspot/cli-lib/errorHandlers');
|
|
22
30
|
const { EXIT_CODES } = require('../../lib/enums/exitCodes');
|
|
23
31
|
|
|
24
32
|
const i18nKey = 'cli.commands.project.subcommands.upload';
|
|
@@ -50,6 +58,26 @@ exports.handler = async options => {
|
|
|
50
58
|
message
|
|
51
59
|
);
|
|
52
60
|
|
|
61
|
+
if (result.error) {
|
|
62
|
+
if (
|
|
63
|
+
isSpecifiedError(result.error, {
|
|
64
|
+
subCategory: ERROR_TYPES.PROJECT_LOCKED,
|
|
65
|
+
})
|
|
66
|
+
) {
|
|
67
|
+
logger.log();
|
|
68
|
+
logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
|
|
69
|
+
logger.log();
|
|
70
|
+
} else {
|
|
71
|
+
logApiErrorInstance(
|
|
72
|
+
result.error,
|
|
73
|
+
new ApiErrorContext({
|
|
74
|
+
accountId,
|
|
75
|
+
projectName: projectConfig.name,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
process.exit(EXIT_CODES.ERROR);
|
|
80
|
+
}
|
|
53
81
|
if (result.buildSucceeded && !result.autodeployEnabled) {
|
|
54
82
|
uiLine();
|
|
55
83
|
logger.log(
|
package/lang/en.lyaml
CHANGED
|
@@ -448,11 +448,10 @@ en:
|
|
|
448
448
|
dev:
|
|
449
449
|
describe: "Start local dev for the current project"
|
|
450
450
|
logs:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
451
|
+
betaMessage: "{{#yellow}}{{#bold}}[beta]{{/bold}}{{/yellow}} HubSpot projects local development"
|
|
452
|
+
learnMoreLink: "Learn more about the projects local dev server"
|
|
453
|
+
nonSandboxWarning: "Testing in a sandbox is strongly recommended. To switch the target account, select an option below or run {{#bold}}`hs accounts use`{{/bold}} before running the command again."
|
|
454
454
|
placeholderAccountSelection: "Using default account as target account (for now)"
|
|
455
|
-
prodAccountWarning: "Targeting a non-sandbox account is risky because this command will upload changes to the target account. We recommend picking a sandbox account for any work that has not been finalized or is undergoing testing prior to deploying to production."
|
|
456
455
|
projectMustExistExplanation: "The project {{ projectName }} does not exist in the target account {{ accountIdentifier}}. This command requires the project to exist in the target account."
|
|
457
456
|
choseNotToCreateProject: "Exiting because this command requires the project to exist in the target account."
|
|
458
457
|
initialUploadMessage: "HubSpot Local Dev Server Startup"
|
|
@@ -461,12 +460,15 @@ en:
|
|
|
461
460
|
createdProject: "Created project {{ projectName }} in {{ accountIdentifier }}"
|
|
462
461
|
failedToCreateProject: "Failed to create project in the target account."
|
|
463
462
|
startupMessage: "Starting local dev server for {{#bold}}{{ projectName }}{{/bold}} ..."
|
|
464
|
-
startupFailed: "Failed to initialize local dev server"
|
|
465
463
|
prompt:
|
|
466
464
|
createProject: "Create new project {{ projectName}} in {{#bold}}[{{ accountIdentifier }}]{{/bold}}?"
|
|
467
465
|
targetNonSandbox: "Continue testing in a non-sandbox account?"
|
|
466
|
+
options:
|
|
467
|
+
port:
|
|
468
|
+
describe: "The port that the running server will listen on"
|
|
468
469
|
errors:
|
|
469
470
|
noProjectConfig: "No project detected. Please run this command again from a project directory."
|
|
471
|
+
projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project dev`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
|
|
470
472
|
examples:
|
|
471
473
|
default: "Start local dev for the current project"
|
|
472
474
|
create:
|
|
@@ -558,6 +560,8 @@ en:
|
|
|
558
560
|
buildSucceeded: "Build #{{ buildId }} succeeded\n"
|
|
559
561
|
readyToGoLive: "🚀 Ready to take your project live?"
|
|
560
562
|
runCommand: "Run `{{ command }}`"
|
|
563
|
+
errors:
|
|
564
|
+
projectLockedError: "Your project is locked. This may mean that another user is running the {{#bold}}`hs project dev`{{/bold}} command for this project. If this is you, unlock the project in Projects UI."
|
|
561
565
|
options:
|
|
562
566
|
forceCreate:
|
|
563
567
|
describe: "Automatically create project if it does not exist"
|
|
@@ -566,15 +570,6 @@ en:
|
|
|
566
570
|
positionals:
|
|
567
571
|
path:
|
|
568
572
|
describe: "Path to a project folder"
|
|
569
|
-
unlock:
|
|
570
|
-
describe: "Unlock a locked project"
|
|
571
|
-
examples:
|
|
572
|
-
default: "Unlock a locked project in the myProjectsFolder folder"
|
|
573
|
-
logs:
|
|
574
|
-
unlockSucceeded: "Successfully unlocked the project"
|
|
575
|
-
positionals:
|
|
576
|
-
path:
|
|
577
|
-
describe: "Path to a project folder"
|
|
578
573
|
watch:
|
|
579
574
|
describe: "Watch your local project for changes and automatically upload changed files to a new build in HubSpot"
|
|
580
575
|
examples:
|
|
@@ -841,6 +836,8 @@ en:
|
|
|
841
836
|
options:
|
|
842
837
|
describe: "Options to pass to javascript fields files"
|
|
843
838
|
lib:
|
|
839
|
+
DevServerManager:
|
|
840
|
+
portConflict: "The port {{ port }} is already in use."
|
|
844
841
|
LocalDevManager:
|
|
845
842
|
exitingStart: "Stopping local dev server ..."
|
|
846
843
|
exitingSucceed: "Successfully exited"
|
|
@@ -848,6 +845,8 @@ en:
|
|
|
848
845
|
previousStagingBuildCancelled: "Failed to create a staging build because the project was already locked. It is now unlocked. Run the command again."
|
|
849
846
|
cancelledFromUI: "The dev process has been cancelled from the UI. Any changes made since cancelling have not been uploaded. To resume dev mode, rerun {{#yellow}}`hs project dev`{{/yellow}}."
|
|
850
847
|
header:
|
|
848
|
+
betaMessage: "{{#yellow}}{{#bold}}[beta]{{/bold}}{{/yellow}} HubSpot projects local development"
|
|
849
|
+
learnMoreLink: "Learn more about the projects local dev server"
|
|
851
850
|
running: "Running {{ projectName}} locally on {{ accountIdentifier }}, waiting for project file changes ..."
|
|
852
851
|
quitHelper: "Press {{#bold}}'q'{{/bold}} to stop the local dev server"
|
|
853
852
|
viewInHubSpotLink: "View in HubSpot"
|
|
@@ -860,20 +859,25 @@ en:
|
|
|
860
859
|
manualUpload: "{{#bold}}Status:{{/bold}} {{#green}}Manually uploading pending changes{{/green}}"
|
|
861
860
|
upload:
|
|
862
861
|
noUploadsAllowed: "The change to {{ filePath }} requires an upload, but the CLI cannot upload to a project that is using a github integration."
|
|
863
|
-
manualUploadSkipped: "
|
|
864
|
-
|
|
862
|
+
manualUploadSkipped: "Manually upload and deploy project: {{#green}}(n){{/green}}"
|
|
863
|
+
manualUploadConfirmed: "Manually upload and deploy project: {{#green}}(Y){{/green}}"
|
|
864
|
+
manualUploadRequired: "Project file changes require a manual upload and deploy ..."
|
|
865
865
|
manualUploadExplanation1: "{{#yellow}}> Dev server is running on a {{#bold}}non-sandbox account{{/bold}}.{{/yellow}}"
|
|
866
866
|
manualUploadExplanation2: "{{#yellow}}> Uploading changes may overwrite production data.{{/yellow}}"
|
|
867
867
|
manualUploadPrompt: "? Manually upload and deploy project? {{#green}}Y/n{{/green}}"
|
|
868
|
-
|
|
868
|
+
uploadingAddChange: "[INFO] Uploading {{ filePath }}"
|
|
869
|
+
uploadedAddChange: "[INFO] Uploaded {{ filePath }}"
|
|
870
|
+
uploadingRemoveChange: "[INFO] Removing {{ filePath }}"
|
|
871
|
+
uploadedRemoveChange: "[INFO] Removed {{ filePath }}"
|
|
869
872
|
uploadingChanges: "{{#bold}}Building and deploying recent changes on {{ accountIdentifier }}{{/bold}}"
|
|
873
|
+
uploadedChangesSucceeded: "{{#bold}}Built and deployed recent changes on {{ accountIdentifier }}{{/bold}}"
|
|
874
|
+
uploadedChangesFailed: "{{#bold}}Failed to build and deploy recent changes on {{ accountIdentifier }}{{/bold}}"
|
|
870
875
|
projects:
|
|
871
876
|
uploadProjectFiles:
|
|
872
877
|
add: "Uploading {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
|
|
873
878
|
fail: "Failed to upload {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
|
|
874
879
|
succeed: "Uploaded {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}"
|
|
875
880
|
buildCreated: "Project \"{{ projectName }}\" uploaded and build #{{ buildId }} created"
|
|
876
|
-
projectLockedError: "\nYour project may be locked by an active `{{#yellow}}hs project watch{{/yellow}}`. Try stopping the `{{#yellow}}watch{{/yellow}}` process before uploading.\nIf that doesn't work, you may need to start and stop a new `{{#yellow}}watch{{/yellow}}` process to unlock the project."
|
|
877
881
|
handleProjectUpload:
|
|
878
882
|
emptySource: "Source directory \"{{ srcDir }}\" is empty. Add files to your project and rerun `{{#yellow}}hs project upload{{/yellow}}` to upload them to HubSpot."
|
|
879
883
|
compressed: "Project files compressed: {{ byteCount }} bytes"
|
|
@@ -955,12 +959,10 @@ en:
|
|
|
955
959
|
describe: "Use environment variable config"
|
|
956
960
|
prompts:
|
|
957
961
|
projectDevTargetAccountPrompt:
|
|
958
|
-
createNewSandboxOption: "
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
defaultAccountNotProd: "Option unavailable because your default account must be a production account."
|
|
963
|
-
sandboxLimit: "Option unavailable because you’ve reached the limit of {{ limit }} development sandboxes."
|
|
962
|
+
createNewSandboxOption: "<Test on a new dev sandbox>"
|
|
963
|
+
chooseDefaultAccountOption: "<{{#bold}}!{{/bold}} Test on this production account {{#bold}}!{{/bold}}>"
|
|
964
|
+
promptMessage: "[--account] Choose a sandbox under {{ accountIdentifier }} to test with:"
|
|
965
|
+
sandboxLimit: "You’ve reached the limit of {{ limit }} development sandboxes"
|
|
964
966
|
projectLogsPrompt:
|
|
965
967
|
projectName:
|
|
966
968
|
message: "[--project] Enter the project name:"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const bodyParser = require('body-parser');
|
|
3
|
+
const cors = require('cors');
|
|
4
|
+
const { i18n } = require('./lang');
|
|
5
|
+
const { getProjectDetailUrl } = require('./projects');
|
|
6
|
+
const { EXIT_CODES } = require('./enums/exitCodes');
|
|
7
|
+
const { logger } = require('@hubspot/cli-lib/logger');
|
|
8
|
+
|
|
9
|
+
const i18nKey = 'cli.lib.DevServerManager';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PORT = 8080;
|
|
12
|
+
|
|
13
|
+
class DevServerManager {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.server = null;
|
|
16
|
+
this.devServers = {};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async iterateDevServers(callback) {
|
|
20
|
+
const serverKeys = Object.keys(this.devServers);
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < serverKeys.length; i++) {
|
|
23
|
+
const serverKey = serverKeys[i];
|
|
24
|
+
const serverInterface = this.devServers[serverKey];
|
|
25
|
+
await callback(serverInterface, serverKey);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async start({ projectConfig, accountId, port }) {
|
|
30
|
+
const app = express();
|
|
31
|
+
|
|
32
|
+
// Install Middleware
|
|
33
|
+
app.use(bodyParser.json({ limit: '50mb' }));
|
|
34
|
+
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
|
35
|
+
app.use(cors());
|
|
36
|
+
|
|
37
|
+
// Configure
|
|
38
|
+
app.set('trust proxy', true);
|
|
39
|
+
|
|
40
|
+
// Initialize a base route
|
|
41
|
+
app.get('/', (req, res) => {
|
|
42
|
+
res.send('HubSpot local dev server');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Initialize URL redirects
|
|
46
|
+
app.get('/hs/project', (req, res) => {
|
|
47
|
+
res.redirect(getProjectDetailUrl(projectConfig.name, accountId));
|
|
48
|
+
});
|
|
49
|
+
app.get('/hs/learnMore', (req, res) => {
|
|
50
|
+
//TODO link to docs
|
|
51
|
+
res.redirect(getProjectDetailUrl(projectConfig.name, accountId));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Initialize component servers
|
|
55
|
+
await this.iterateDevServers(async (serverInterface, serverKey) => {
|
|
56
|
+
if (serverInterface.start) {
|
|
57
|
+
const serverApp = await serverInterface.start(serverKey);
|
|
58
|
+
app.use(`/${serverKey}`, serverApp);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Start server
|
|
63
|
+
this.server = app.listen(port || DEFAULT_PORT).on('error', err => {
|
|
64
|
+
if (err.code === 'EADDRINUSE') {
|
|
65
|
+
logger.error(
|
|
66
|
+
i18n(`${i18nKey}.portConflict`, { port: port || DEFAULT_PORT })
|
|
67
|
+
);
|
|
68
|
+
logger.log();
|
|
69
|
+
process.exit(EXIT_CODES.ERROR);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return this.server.address()
|
|
74
|
+
? `http://localhost:${this.server.address().port}`
|
|
75
|
+
: null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async notify() {
|
|
79
|
+
return { uploadRequired: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async execute() {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async cleanup() {
|
|
87
|
+
if (this.server) {
|
|
88
|
+
await this.server.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = new DevServerManager();
|
package/lib/LocalDevManager.js
CHANGED
|
@@ -22,16 +22,14 @@ const {
|
|
|
22
22
|
queueBuild,
|
|
23
23
|
} = require('@hubspot/cli-lib/api/dfs');
|
|
24
24
|
const SpinniesManager = require('./SpinniesManager');
|
|
25
|
+
const DevServerManager = require('./DevServerManager');
|
|
25
26
|
const { EXIT_CODES } = require('./enums/exitCodes');
|
|
26
|
-
const {
|
|
27
|
-
getProjectDetailUrl,
|
|
28
|
-
pollProjectBuildAndDeploy,
|
|
29
|
-
} = require('./projects');
|
|
27
|
+
const { pollProjectBuildAndDeploy } = require('./projects');
|
|
30
28
|
const { uiAccountDescription, uiLink } = require('./ui');
|
|
31
29
|
|
|
32
30
|
const i18nKey = 'cli.lib.LocalDevManager';
|
|
33
31
|
|
|
34
|
-
const BUILD_DEBOUNCE_TIME =
|
|
32
|
+
const BUILD_DEBOUNCE_TIME = 3500;
|
|
35
33
|
|
|
36
34
|
const WATCH_EVENTS = {
|
|
37
35
|
add: 'add',
|
|
@@ -45,6 +43,7 @@ const UPLOAD_PERMISSIONS = {
|
|
|
45
43
|
manual: 'manual',
|
|
46
44
|
never: 'never',
|
|
47
45
|
};
|
|
46
|
+
|
|
48
47
|
class LocalDevManager {
|
|
49
48
|
constructor(options) {
|
|
50
49
|
this.targetAccountId = options.targetAccountId;
|
|
@@ -53,7 +52,6 @@ class LocalDevManager {
|
|
|
53
52
|
this.uploadPermission =
|
|
54
53
|
options.uploadPermission || UPLOAD_PERMISSIONS.always;
|
|
55
54
|
this.debug = options.debug || false;
|
|
56
|
-
this.mockServers = options.mockServers || false;
|
|
57
55
|
this.projectSourceDir = path.join(
|
|
58
56
|
this.projectDir,
|
|
59
57
|
this.projectConfig.srcDir
|
|
@@ -64,6 +62,8 @@ class LocalDevManager {
|
|
|
64
62
|
this.standbyChanges = [];
|
|
65
63
|
this.debouncedBuild = null;
|
|
66
64
|
this.currentStagedBuildId = null;
|
|
65
|
+
this.port = options.port;
|
|
66
|
+
this.devServerPath = null;
|
|
67
67
|
|
|
68
68
|
if (!this.targetAccountId || !this.projectConfig || !this.projectDir) {
|
|
69
69
|
process.exit(EXIT_CODES.ERROR);
|
|
@@ -90,8 +90,9 @@ class LocalDevManager {
|
|
|
90
90
|
|
|
91
91
|
this.uploadQueue.start();
|
|
92
92
|
|
|
93
|
-
this.logConsoleHeader();
|
|
94
93
|
await this.startServers();
|
|
94
|
+
|
|
95
|
+
this.logConsoleHeader();
|
|
95
96
|
await this.startWatching();
|
|
96
97
|
this.updateKeypressListeners();
|
|
97
98
|
}
|
|
@@ -145,6 +146,26 @@ class LocalDevManager {
|
|
|
145
146
|
|
|
146
147
|
logConsoleHeader() {
|
|
147
148
|
this.spinnies.removeAll();
|
|
149
|
+
this.spinnies.add('betaMessage', {
|
|
150
|
+
text: i18n(`${i18nKey}.header.betaMessage`),
|
|
151
|
+
category: 'header',
|
|
152
|
+
status: 'non-spinnable',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// this.spinnies.add('learnMoreLink', {
|
|
156
|
+
// text: uiLink(
|
|
157
|
+
// i18n(`${i18nKey}.header.learnMoreLink`),
|
|
158
|
+
// this.generateLocalURL(`/hs/learnMore`),
|
|
159
|
+
// { inSpinnies: true }
|
|
160
|
+
// ),
|
|
161
|
+
// category: 'header',
|
|
162
|
+
// status: 'non-spinnable',
|
|
163
|
+
// });
|
|
164
|
+
this.spinnies.add('spacer-1', {
|
|
165
|
+
text: ' ',
|
|
166
|
+
status: 'non-spinnable',
|
|
167
|
+
category: 'header',
|
|
168
|
+
});
|
|
148
169
|
this.spinnies.add('devModeRunning', {
|
|
149
170
|
text: i18n(`${i18nKey}.header.running`, {
|
|
150
171
|
accountIdentifier: uiAccountDescription(this.targetAccountId),
|
|
@@ -159,23 +180,17 @@ class LocalDevManager {
|
|
|
159
180
|
indent: 1,
|
|
160
181
|
category: 'header',
|
|
161
182
|
});
|
|
162
|
-
const projectDetailUrl = getProjectDetailUrl(
|
|
163
|
-
this.projectConfig.name,
|
|
164
|
-
this.targetAccountId
|
|
165
|
-
);
|
|
166
183
|
this.spinnies.add('viewInHubSpotLink', {
|
|
167
184
|
text: uiLink(
|
|
168
185
|
i18n(`${i18nKey}.header.viewInHubSpotLink`),
|
|
169
|
-
|
|
170
|
-
{
|
|
171
|
-
inSpinnies: true,
|
|
172
|
-
}
|
|
186
|
+
this.generateLocalURL(`/hs/project`),
|
|
187
|
+
{ inSpinnies: true }
|
|
173
188
|
),
|
|
174
189
|
status: 'non-spinnable',
|
|
175
190
|
indent: 1,
|
|
176
191
|
category: 'header',
|
|
177
192
|
});
|
|
178
|
-
this.spinnies.add('spacer-
|
|
193
|
+
this.spinnies.add('spacer-2', {
|
|
179
194
|
text: ' ',
|
|
180
195
|
status: 'non-spinnable',
|
|
181
196
|
category: 'header',
|
|
@@ -202,24 +217,29 @@ class LocalDevManager {
|
|
|
202
217
|
handleKeypress(async key => {
|
|
203
218
|
if ((key.ctrl && key.name === 'c') || key.name === 'q') {
|
|
204
219
|
this.stop();
|
|
205
|
-
} else if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
220
|
+
} else if (
|
|
221
|
+
(key.name === 'y' || key.name === 'n') &&
|
|
222
|
+
this.uploadPermission === UPLOAD_PERMISSIONS.manual &&
|
|
223
|
+
this.hasAnyUnsupportedStandbyChanges()
|
|
224
|
+
) {
|
|
225
|
+
this.spinnies.remove('manualUploadRequired');
|
|
226
|
+
this.spinnies.remove('manualUploadExplanation1');
|
|
227
|
+
this.spinnies.remove('manualUploadExplanation2');
|
|
228
|
+
this.spinnies.remove('manualUploadPrompt');
|
|
229
|
+
|
|
230
|
+
if (key.name === 'y') {
|
|
231
|
+
this.spinnies.add(null, {
|
|
232
|
+
text: i18n(`${i18nKey}.upload.manualUploadConfirmed`),
|
|
233
|
+
status: 'succeed',
|
|
234
|
+
succeedColor: 'white',
|
|
235
|
+
noIndent: true,
|
|
236
|
+
});
|
|
211
237
|
this.updateDevModeStatus('manualUpload');
|
|
212
238
|
await this.createNewStagingBuild();
|
|
213
239
|
await this.flushStandbyChanges();
|
|
214
240
|
await this.queueBuild();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
this.uploadPermission === UPLOAD_PERMISSIONS.manual &&
|
|
219
|
-
this.hasAnyUnsupportedStandbyChanges()
|
|
220
|
-
) {
|
|
221
|
-
this.clearConsoleContent();
|
|
222
|
-
this.spinnies.add('manualUploadSkipped', {
|
|
241
|
+
} else if (key.name === 'n') {
|
|
242
|
+
this.spinnies.add(null, {
|
|
223
243
|
text: i18n(`${i18nKey}.upload.manualUploadSkipped`),
|
|
224
244
|
status: 'fail',
|
|
225
245
|
failColor: 'white',
|
|
@@ -230,6 +250,10 @@ class LocalDevManager {
|
|
|
230
250
|
});
|
|
231
251
|
}
|
|
232
252
|
|
|
253
|
+
generateLocalURL(path) {
|
|
254
|
+
return this.devServerPath ? `${this.devServerPath}${path}` : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
233
257
|
updateDevModeStatus(langKey) {
|
|
234
258
|
this.spinnies.update('devModeStatus', {
|
|
235
259
|
text: i18n(`${i18nKey}.header.status.${langKey}`),
|
|
@@ -239,13 +263,6 @@ class LocalDevManager {
|
|
|
239
263
|
}
|
|
240
264
|
|
|
241
265
|
async pauseUploadQueue() {
|
|
242
|
-
this.spinnies.add('uploading', {
|
|
243
|
-
text: i18n(`${i18nKey}.upload.uploadingChanges`, {
|
|
244
|
-
accountIdentifier: uiAccountDescription(this.targetAccountId),
|
|
245
|
-
}),
|
|
246
|
-
noIndent: true,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
266
|
this.uploadQueue.pause();
|
|
250
267
|
await this.uploadQueue.onIdle();
|
|
251
268
|
}
|
|
@@ -297,11 +314,13 @@ class LocalDevManager {
|
|
|
297
314
|
remotePath: path.relative(this.projectSourceDir, filePath),
|
|
298
315
|
};
|
|
299
316
|
|
|
300
|
-
const
|
|
317
|
+
const notifyResponse = await this.notifyServers(changeInfo);
|
|
301
318
|
|
|
302
|
-
if (
|
|
319
|
+
if (!notifyResponse.uploadRequired) {
|
|
303
320
|
this.updateDevModeStatus('supportedChange');
|
|
304
321
|
this.addChangeToStandbyQueue({ ...changeInfo, supported: true });
|
|
322
|
+
|
|
323
|
+
await this.executeServers(notifyResponse, changeInfo);
|
|
305
324
|
return;
|
|
306
325
|
}
|
|
307
326
|
|
|
@@ -311,13 +330,7 @@ class LocalDevManager {
|
|
|
311
330
|
}
|
|
312
331
|
|
|
313
332
|
if (this.uploadQueue.isPaused) {
|
|
314
|
-
|
|
315
|
-
!this.standbyChanges.find(
|
|
316
|
-
changeInfo => changeInfo.filePath === filePath
|
|
317
|
-
)
|
|
318
|
-
) {
|
|
319
|
-
this.addChangeToStandbyQueue({ ...changeInfo, supported: false });
|
|
320
|
-
}
|
|
333
|
+
this.addChangeToStandbyQueue({ ...changeInfo, supported: false });
|
|
321
334
|
} else {
|
|
322
335
|
await this.flushStandbyChanges();
|
|
323
336
|
|
|
@@ -334,7 +347,6 @@ class LocalDevManager {
|
|
|
334
347
|
handlePreventedUpload(changeInfo) {
|
|
335
348
|
const { remotePath } = changeInfo;
|
|
336
349
|
|
|
337
|
-
this.clearConsoleContent();
|
|
338
350
|
if (this.uploadPermission === UPLOAD_PERMISSIONS.never) {
|
|
339
351
|
this.updateDevModeStatus('noUploadsAllowed');
|
|
340
352
|
|
|
@@ -388,35 +400,63 @@ class LocalDevManager {
|
|
|
388
400
|
logger.debug(`File ignored: ${filePath}`);
|
|
389
401
|
return;
|
|
390
402
|
}
|
|
391
|
-
|
|
403
|
+
|
|
404
|
+
const existingIndex = this.standbyChanges.findIndex(
|
|
405
|
+
standyChangeInfo => standyChangeInfo.filePath === filePath
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
if (existingIndex > -1) {
|
|
409
|
+
// Make sure the most recent event to this file is the one that gets acted on
|
|
410
|
+
this.standbyChanges[existingIndex].event = event;
|
|
411
|
+
} else {
|
|
412
|
+
this.standbyChanges.push(changeInfo);
|
|
413
|
+
}
|
|
392
414
|
}
|
|
393
415
|
|
|
394
416
|
async sendChanges(changeInfo) {
|
|
395
417
|
const { event, filePath, remotePath } = changeInfo;
|
|
396
418
|
|
|
397
|
-
this.spinnies.add(filePath, {
|
|
398
|
-
text: i18n(`${i18nKey}.upload.uploadingChange`, {
|
|
399
|
-
filePath: remotePath,
|
|
400
|
-
}),
|
|
401
|
-
status: 'non-spinnable',
|
|
402
|
-
});
|
|
403
419
|
try {
|
|
404
420
|
if (event === WATCH_EVENTS.add || event === WATCH_EVENTS.change) {
|
|
421
|
+
const spinniesKey = this.spinnies.add(null, {
|
|
422
|
+
text: i18n(`${i18nKey}.upload.uploadingAddChange`, {
|
|
423
|
+
filePath: remotePath,
|
|
424
|
+
}),
|
|
425
|
+
status: 'non-spinnable',
|
|
426
|
+
});
|
|
405
427
|
await uploadFileToBuild(
|
|
406
428
|
this.targetAccountId,
|
|
407
429
|
this.projectConfig.name,
|
|
408
430
|
filePath,
|
|
409
431
|
remotePath
|
|
410
432
|
);
|
|
433
|
+
this.spinnies.update(spinniesKey, {
|
|
434
|
+
text: i18n(`${i18nKey}.upload.uploadedAddChange`, {
|
|
435
|
+
filePath: remotePath,
|
|
436
|
+
}),
|
|
437
|
+
status: 'non-spinnable',
|
|
438
|
+
});
|
|
411
439
|
} else if (
|
|
412
440
|
event === WATCH_EVENTS.unlink ||
|
|
413
441
|
event === WATCH_EVENTS.unlinkDir
|
|
414
442
|
) {
|
|
443
|
+
const spinniesKey = this.spinnies.add(null, {
|
|
444
|
+
text: i18n(`${i18nKey}.upload.uploadingRemoveChange`, {
|
|
445
|
+
filePath: remotePath,
|
|
446
|
+
}),
|
|
447
|
+
status: 'non-spinnable',
|
|
448
|
+
});
|
|
415
449
|
await deleteFileFromBuild(
|
|
416
450
|
this.targetAccountId,
|
|
417
451
|
this.projectConfig.name,
|
|
418
452
|
remotePath
|
|
419
453
|
);
|
|
454
|
+
this.spinnies.update(spinniesKey, {
|
|
455
|
+
text: i18n(`${i18nKey}.upload.uploadedRemoveChange`, {
|
|
456
|
+
filePath: remotePath,
|
|
457
|
+
}),
|
|
458
|
+
status: 'non-spinnable',
|
|
459
|
+
});
|
|
420
460
|
}
|
|
421
461
|
} catch (err) {
|
|
422
462
|
logger.debug(err);
|
|
@@ -439,6 +479,13 @@ class LocalDevManager {
|
|
|
439
479
|
}
|
|
440
480
|
|
|
441
481
|
async queueBuild() {
|
|
482
|
+
const spinniesKey = this.spinnies.add(null, {
|
|
483
|
+
text: i18n(`${i18nKey}.upload.uploadingChanges`, {
|
|
484
|
+
accountIdentifier: uiAccountDescription(this.targetAccountId),
|
|
485
|
+
}),
|
|
486
|
+
noIndent: true,
|
|
487
|
+
});
|
|
488
|
+
|
|
442
489
|
await this.pauseUploadQueue();
|
|
443
490
|
|
|
444
491
|
try {
|
|
@@ -464,7 +511,7 @@ class LocalDevManager {
|
|
|
464
511
|
return;
|
|
465
512
|
}
|
|
466
513
|
|
|
467
|
-
await pollProjectBuildAndDeploy(
|
|
514
|
+
const result = await pollProjectBuildAndDeploy(
|
|
468
515
|
this.targetAccountId,
|
|
469
516
|
this.projectConfig,
|
|
470
517
|
null,
|
|
@@ -472,12 +519,31 @@ class LocalDevManager {
|
|
|
472
519
|
true
|
|
473
520
|
);
|
|
474
521
|
|
|
522
|
+
if (result && result.succeeded) {
|
|
523
|
+
this.spinnies.succeed(spinniesKey, {
|
|
524
|
+
text: i18n(`${i18nKey}.upload.uploadedChangesSucceeded`, {
|
|
525
|
+
accountIdentifier: uiAccountDescription(this.targetAccountId),
|
|
526
|
+
}),
|
|
527
|
+
succeedColor: 'white',
|
|
528
|
+
noIndent: true,
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
this.spinnies.fail(spinniesKey, {
|
|
532
|
+
text: i18n(`${i18nKey}.upload.uploadedChangesFailed`, {
|
|
533
|
+
accountIdentifier: uiAccountDescription(this.targetAccountId),
|
|
534
|
+
}),
|
|
535
|
+
failColor: 'white',
|
|
536
|
+
noIndent: true,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
this.spinnies.removeAll({ targetCategory: 'projectPollStatus' });
|
|
541
|
+
|
|
475
542
|
if (this.uploadPermission === UPLOAD_PERMISSIONS.always) {
|
|
476
543
|
await this.createNewStagingBuild();
|
|
477
544
|
}
|
|
478
545
|
|
|
479
546
|
this.uploadQueue.start();
|
|
480
|
-
this.clearConsoleContent();
|
|
481
547
|
|
|
482
548
|
if (this.hasAnyUnsupportedStandbyChanges()) {
|
|
483
549
|
this.flushStandbyChanges();
|
|
@@ -510,23 +576,24 @@ class LocalDevManager {
|
|
|
510
576
|
}
|
|
511
577
|
|
|
512
578
|
async startServers() {
|
|
513
|
-
|
|
514
|
-
|
|
579
|
+
this.devServerPath = await DevServerManager.start({
|
|
580
|
+
accountId: this.targetAccountId,
|
|
581
|
+
projectConfig: this.projectConfig,
|
|
582
|
+
port: this.port,
|
|
583
|
+
});
|
|
515
584
|
}
|
|
516
585
|
|
|
517
586
|
async notifyServers(changeInfo) {
|
|
518
|
-
const
|
|
587
|
+
const notifyResponse = await DevServerManager.notify(changeInfo);
|
|
588
|
+
return notifyResponse;
|
|
589
|
+
}
|
|
519
590
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return !remotePath.endsWith('app.json');
|
|
523
|
-
}
|
|
524
|
-
return false;
|
|
591
|
+
async executeServers(notifyResponse, changeInfo) {
|
|
592
|
+
await DevServerManager.execute(notifyResponse, changeInfo);
|
|
525
593
|
}
|
|
526
594
|
|
|
527
595
|
async cleanupServers() {
|
|
528
|
-
|
|
529
|
-
return;
|
|
596
|
+
await DevServerManager.cleanup();
|
|
530
597
|
}
|
|
531
598
|
}
|
|
532
599
|
|
package/lib/SpinniesManager.js
CHANGED
|
@@ -51,18 +51,23 @@ class SpinniesManager {
|
|
|
51
51
|
const { category, isParent, noIndent, ...rest } = options;
|
|
52
52
|
const originalIndent = rest.indent || 0;
|
|
53
53
|
|
|
54
|
+
// Support adding generic spinnies lines without specifying a key
|
|
55
|
+
const uniqueKey = key || `${Date.now()}`;
|
|
56
|
+
|
|
54
57
|
if (category) {
|
|
55
|
-
this.addKeyToCategory(
|
|
58
|
+
this.addKeyToCategory(uniqueKey, category);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
this.spinnies.add(
|
|
61
|
+
this.spinnies.add(uniqueKey, {
|
|
59
62
|
...rest,
|
|
60
63
|
indent: this.parentKey && !noIndent ? originalIndent + 1 : originalIndent,
|
|
61
64
|
});
|
|
62
65
|
|
|
63
66
|
if (isParent) {
|
|
64
|
-
this.parentKey =
|
|
67
|
+
this.parentKey = uniqueKey;
|
|
65
68
|
}
|
|
69
|
+
|
|
70
|
+
return uniqueKey;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
remove(key) {
|
|
@@ -79,10 +84,14 @@ class SpinniesManager {
|
|
|
79
84
|
* Removes all spinnies instances
|
|
80
85
|
* @param {string} preserveCategory - do not remove spinnies with a matching category
|
|
81
86
|
*/
|
|
82
|
-
removeAll({ preserveCategory = null } = {}) {
|
|
87
|
+
removeAll({ preserveCategory = null, targetCategory = null } = {}) {
|
|
83
88
|
if (this.spinnies) {
|
|
84
89
|
Object.keys(this.spinnies.spinners).forEach(key => {
|
|
85
|
-
if (
|
|
90
|
+
if (targetCategory) {
|
|
91
|
+
if (this.getCategoryForKey(key) === targetCategory) {
|
|
92
|
+
this.remove(key);
|
|
93
|
+
}
|
|
94
|
+
} else if (
|
|
86
95
|
!preserveCategory ||
|
|
87
96
|
this.getCategoryForKey(key) !== preserveCategory
|
|
88
97
|
) {
|
package/lib/projects.js
CHANGED
|
@@ -10,7 +10,6 @@ const { getHubSpotWebsiteOrigin } = require('@hubspot/cli-lib/lib/urls');
|
|
|
10
10
|
const {
|
|
11
11
|
ENVIRONMENTS,
|
|
12
12
|
FEEDBACK_INTERVAL,
|
|
13
|
-
ERROR_TYPES,
|
|
14
13
|
POLLING_DELAY,
|
|
15
14
|
PROJECT_BUILD_TEXT,
|
|
16
15
|
PROJECT_DEPLOY_TEXT,
|
|
@@ -320,6 +319,7 @@ const uploadProjectFiles = async (
|
|
|
320
319
|
});
|
|
321
320
|
|
|
322
321
|
let buildId;
|
|
322
|
+
let error;
|
|
323
323
|
|
|
324
324
|
try {
|
|
325
325
|
const upload = await uploadProject(
|
|
@@ -352,20 +352,10 @@ const uploadProjectFiles = async (
|
|
|
352
352
|
}),
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
err,
|
|
357
|
-
new ApiErrorContext({
|
|
358
|
-
accountId,
|
|
359
|
-
projectName,
|
|
360
|
-
})
|
|
361
|
-
);
|
|
362
|
-
if (err.error.subCategory === ERROR_TYPES.PROJECT_LOCKED) {
|
|
363
|
-
logger.log(i18n(`${i18nKey}.uploadProjectFiles.projectLockedError`));
|
|
364
|
-
}
|
|
365
|
-
process.exit(EXIT_CODES.ERROR);
|
|
355
|
+
error = err;
|
|
366
356
|
}
|
|
367
357
|
|
|
368
|
-
return { buildId };
|
|
358
|
+
return { buildId, error };
|
|
369
359
|
};
|
|
370
360
|
|
|
371
361
|
const pollProjectBuildAndDeploy = async (
|
|
@@ -478,7 +468,7 @@ const handleProjectUpload = async (
|
|
|
478
468
|
|
|
479
469
|
const result = new Promise(resolve =>
|
|
480
470
|
output.on('close', async function() {
|
|
481
|
-
let
|
|
471
|
+
let uploadResult = {};
|
|
482
472
|
|
|
483
473
|
logger.debug(
|
|
484
474
|
i18n(`${i18nKey}.handleProjectUpload.compressed`, {
|
|
@@ -486,22 +476,24 @@ const handleProjectUpload = async (
|
|
|
486
476
|
})
|
|
487
477
|
);
|
|
488
478
|
|
|
489
|
-
const { buildId } = await uploadProjectFiles(
|
|
479
|
+
const { buildId, error } = await uploadProjectFiles(
|
|
490
480
|
accountId,
|
|
491
481
|
projectConfig.name,
|
|
492
482
|
tempFile.name,
|
|
493
483
|
uploadMessage
|
|
494
484
|
);
|
|
495
485
|
|
|
496
|
-
if (
|
|
497
|
-
|
|
486
|
+
if (error) {
|
|
487
|
+
uploadResult.error = error;
|
|
488
|
+
} else if (callbackFunc) {
|
|
489
|
+
uploadResult = await callbackFunc(
|
|
498
490
|
accountId,
|
|
499
491
|
projectConfig,
|
|
500
492
|
tempFile,
|
|
501
493
|
buildId
|
|
502
494
|
);
|
|
503
495
|
}
|
|
504
|
-
resolve(
|
|
496
|
+
resolve(uploadResult);
|
|
505
497
|
})
|
|
506
498
|
);
|
|
507
499
|
|
|
@@ -558,6 +550,7 @@ const makePollTaskStatusFunc = ({
|
|
|
558
550
|
succeedColor: 'white',
|
|
559
551
|
failColor: 'white',
|
|
560
552
|
failPrefix: chalk.bold('!'),
|
|
553
|
+
category: 'projectPollStatus',
|
|
561
554
|
});
|
|
562
555
|
|
|
563
556
|
const [
|
|
@@ -618,6 +611,7 @@ const makePollTaskStatusFunc = ({
|
|
|
618
611
|
indent,
|
|
619
612
|
succeedColor: 'white',
|
|
620
613
|
failColor: 'white',
|
|
614
|
+
category: 'projectPollStatus',
|
|
621
615
|
});
|
|
622
616
|
};
|
|
623
617
|
|
|
@@ -9,90 +9,64 @@ const { logger } = require('@hubspot/cli-lib/logger');
|
|
|
9
9
|
const i18nKey = 'cli.lib.prompts.projectDevTargetAccountPrompt';
|
|
10
10
|
|
|
11
11
|
const mapSandboxAccount = accountConfig => ({
|
|
12
|
-
name: getAccountName(accountConfig),
|
|
12
|
+
name: getAccountName(accountConfig, false),
|
|
13
13
|
value: {
|
|
14
14
|
targetAccountId: getAccountId(accountConfig.name),
|
|
15
|
-
chooseNonSandbox: false,
|
|
16
15
|
createNewSandbox: false,
|
|
17
16
|
},
|
|
18
17
|
});
|
|
19
18
|
|
|
20
|
-
const selectTargetAccountPrompt = async (
|
|
21
|
-
|
|
22
|
-
defaultAccountConfig
|
|
23
|
-
nonSandbox = false
|
|
24
|
-
) => {
|
|
25
|
-
let choices;
|
|
19
|
+
const selectTargetAccountPrompt = async (accounts, defaultAccountConfig) => {
|
|
20
|
+
let sandboxUsage = {};
|
|
21
|
+
const defaultAccountId = getAccountId(defaultAccountConfig.name);
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
sandboxUsage['DEVELOPER'] &&
|
|
56
|
-
sandboxUsage['DEVELOPER'].available === 0
|
|
57
|
-
) {
|
|
58
|
-
disabledMessage = i18n(`${i18nKey}.sandboxLimit`, {
|
|
59
|
-
limit: sandboxUsage['DEVELOPER'].limit,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
// Order choices by Create new -> Developer Sandbox -> Standard Sandbox -> Non sandbox
|
|
63
|
-
choices = [
|
|
64
|
-
{
|
|
65
|
-
name: i18n(`${i18nKey}.createNewSandboxOption`),
|
|
66
|
-
value: {
|
|
67
|
-
targetAccountId: null,
|
|
68
|
-
chooseNonSandbox: false,
|
|
69
|
-
createNewSandbox: true,
|
|
70
|
-
},
|
|
71
|
-
disabled: disabledMessage,
|
|
23
|
+
try {
|
|
24
|
+
sandboxUsage = await getSandboxUsageLimits(defaultAccountId);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
logger.debug('Unable to fetch sandbox usage limits: ', err);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sandboxAccounts = accounts.reverse().filter(isSandbox);
|
|
30
|
+
let disabledMessage = false;
|
|
31
|
+
|
|
32
|
+
if (sandboxUsage['DEVELOPER'] && sandboxUsage['DEVELOPER'].available === 0) {
|
|
33
|
+
disabledMessage = i18n(`${i18nKey}.sandboxLimit`, {
|
|
34
|
+
limit: sandboxUsage['DEVELOPER'].limit,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Order choices by Developer Sandbox -> Standard Sandbox
|
|
39
|
+
const choices = [
|
|
40
|
+
...sandboxAccounts
|
|
41
|
+
.filter(a => a.sandboxAccountType === 'DEVELOPER')
|
|
42
|
+
.map(mapSandboxAccount),
|
|
43
|
+
...sandboxAccounts
|
|
44
|
+
.filter(a => a.sandboxAccountType === 'STANDARD')
|
|
45
|
+
.map(mapSandboxAccount),
|
|
46
|
+
{
|
|
47
|
+
name: i18n(`${i18nKey}.createNewSandboxOption`),
|
|
48
|
+
value: {
|
|
49
|
+
targetAccountId: null,
|
|
50
|
+
createNewSandbox: true,
|
|
72
51
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
name: i18n(`${i18nKey}.chooseNonSandboxOption`),
|
|
81
|
-
value: {
|
|
82
|
-
targetAccountId: null,
|
|
83
|
-
chooseNonSandbox: true,
|
|
84
|
-
createNewSandbox: false,
|
|
85
|
-
},
|
|
52
|
+
disabled: disabledMessage,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: i18n(`${i18nKey}.chooseDefaultAccountOption`),
|
|
56
|
+
value: {
|
|
57
|
+
targetAccountId: defaultAccountId,
|
|
58
|
+
createNewSandbox: false,
|
|
86
59
|
},
|
|
87
|
-
|
|
88
|
-
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
89
63
|
const { targetAccountInfo } = await promptUser([
|
|
90
64
|
{
|
|
91
65
|
name: 'targetAccountInfo',
|
|
92
66
|
type: 'list',
|
|
93
|
-
message:
|
|
94
|
-
|
|
95
|
-
|
|
67
|
+
message: i18n(`${i18nKey}.promptMessage`, {
|
|
68
|
+
accountIdentifier: uiAccountDescription(defaultAccountId),
|
|
69
|
+
}),
|
|
96
70
|
choices,
|
|
97
71
|
},
|
|
98
72
|
]);
|
package/lib/sandboxes.js
CHANGED
|
@@ -55,13 +55,16 @@ const getSandboxTypeAsString = type =>
|
|
|
55
55
|
const isSandbox = config =>
|
|
56
56
|
config.sandboxAccountType && config.sandboxAccountType !== null;
|
|
57
57
|
|
|
58
|
-
function getAccountName(config) {
|
|
58
|
+
function getAccountName(config, bold = true) {
|
|
59
59
|
const sandboxName = `[${getSandboxTypeAsString(
|
|
60
60
|
config.sandboxAccountType
|
|
61
61
|
)} sandbox] `;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
|
|
63
|
+
const message = `${config.name} ${isSandbox(config) ? sandboxName : ''}(${
|
|
64
|
+
config.portalId
|
|
65
|
+
})`;
|
|
66
|
+
|
|
67
|
+
return bold ? chalk.bold(message) : message;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
function getHasSandboxesByType(parentAccountConfig, type) {
|
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.5",
|
|
4
4
|
"description": "CLI for working with HubSpot",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,16 +8,18 @@
|
|
|
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.8-beta.
|
|
11
|
+
"@hubspot/cli-lib": "^4.1.12",
|
|
12
|
+
"@hubspot/serverless-dev-runtime": "4.1.8-beta.4",
|
|
13
13
|
"archiver": "^5.3.0",
|
|
14
|
+
"body-parser": "^1.19.0",
|
|
14
15
|
"chalk": "^4.1.2",
|
|
15
16
|
"chokidar": "^3.0.1",
|
|
16
17
|
"cli-progress": "^3.11.2",
|
|
18
|
+
"cors": "^2.8.5",
|
|
17
19
|
"express": "^4.17.1",
|
|
18
20
|
"findup-sync": "^4.0.0",
|
|
19
21
|
"fs-extra": "^8.1.0",
|
|
20
|
-
"inquirer": "
|
|
22
|
+
"inquirer": "8.2.0",
|
|
21
23
|
"js-yaml": "^4.1.0",
|
|
22
24
|
"moment": "^2.29.1",
|
|
23
25
|
"open": "^7.0.3",
|
|
@@ -41,5 +43,5 @@
|
|
|
41
43
|
"publishConfig": {
|
|
42
44
|
"access": "public"
|
|
43
45
|
},
|
|
44
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "5b5d25bf5e3a4d29b516dbc31195f4cd7051b898"
|
|
45
47
|
}
|