@hubspot/cli 5.2.1-beta.0 → 5.2.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -5,6 +5,7 @@ const updateNotifier = require('update-notifier');
5
5
  const chalk = require('chalk');
6
6
 
7
7
  const { logger } = require('@hubspot/local-dev-lib/logger');
8
+ const { addUserAgentHeader } = require('@hubspot/local-dev-lib/http');
8
9
  const { logErrorInstance } = require('../lib/errorHandlers/standardErrors');
9
10
  const { setLogLevel, getCommandName } = require('../lib/commonOpts');
10
11
  const {
@@ -110,9 +111,13 @@ const performChecks = argv => {
110
111
  }
111
112
  };
112
113
 
114
+ const setRequestHeaders = () => {
115
+ addUserAgentHeader('HubSpot CLI', pkg.version);
116
+ };
117
+
113
118
  const argv = yargs
114
119
  .usage('The command line interface to interact with HubSpot.')
115
- .middleware([setLogLevel])
120
+ .middleware([setLogLevel, setRequestHeaders])
116
121
  .exitProcess(false)
117
122
  .fail(handleFailure)
118
123
  .option('debug', {
@@ -13,7 +13,6 @@ const {
13
13
  addUseEnvironmentOptions,
14
14
  addTestingOptions,
15
15
  } = require('../../lib/commonOpts');
16
- const { getAccountName } = require('../../lib/sandboxes');
17
16
  const { promptUser } = require('../../lib/prompts/promptUtils');
18
17
  const { getTableContents } = require('../../lib/ui/table');
19
18
  const SpinniesManager = require('../../lib/ui/SpinniesManager');
@@ -21,6 +20,7 @@ const { getConfig, deleteAccount } = require('@hubspot/local-dev-lib/config');
21
20
  const {
22
21
  isSpecifiedHubSpotAuthError,
23
22
  } = require('@hubspot/local-dev-lib/errors/apiErrors');
23
+ const { uiAccountDescription } = require('../../lib/ui');
24
24
 
25
25
  const i18nKey = 'cli.commands.accounts.subcommands.clean';
26
26
 
@@ -87,7 +87,7 @@ exports.handler = async options => {
87
87
  });
88
88
  logger.log(
89
89
  getTableContents(
90
- accountsToRemove.map(p => [getAccountName(p)]),
90
+ accountsToRemove.map(p => [uiAccountDescription(p)]),
91
91
  { border: { bodyLeft: ' ' } }
92
92
  )
93
93
  );
@@ -111,7 +111,7 @@ exports.handler = async options => {
111
111
  await deleteAccount(accountToRemove.name);
112
112
  logger.log(
113
113
  i18n(`${i18nKey}.removeSuccess`, {
114
- accountName: getAccountName(accountToRemove),
114
+ accountName: uiAccountDescription(accountToRemove),
115
115
  })
116
116
  );
117
117
  }
@@ -9,8 +9,13 @@ const {
9
9
  } = require('../../lib/commonOpts');
10
10
  const { trackCommandUsage } = require('../../lib/usageTracking');
11
11
  const { loadAndValidateOptions } = require('../../lib/validation');
12
- const { isSandbox, getSandboxName } = require('../../lib/sandboxes');
13
- const { isDeveloperTestAccount } = require('../../lib/developerTestAccounts');
12
+ const { getSandboxName } = require('../../lib/sandboxes');
13
+ const {
14
+ isSandbox,
15
+ isDeveloperTestAccount,
16
+ isAppDeveloperAccount,
17
+ } = require('../../lib/accountTypes');
18
+
14
19
  const { i18n } = require('../../lib/lang');
15
20
  const {
16
21
  HUBSPOT_ACCOUNT_TYPES,
@@ -53,21 +58,28 @@ const sortAndMapPortals = portals => {
53
58
 
54
59
  const getPortalData = mappedPortalData => {
55
60
  const portalData = [];
56
- Object.values(mappedPortalData).forEach(set => {
61
+ Object.entries(mappedPortalData).forEach(([key, set]) => {
62
+ const hasParentPortal = set.filter(
63
+ p => p.portalId === parseInt(key, 10)
64
+ )[0];
57
65
  set.forEach(portal => {
58
66
  let name = portal.name;
59
67
  if (isSandbox(portal)) {
60
68
  name = `${portal.name} ${getSandboxName(portal)}`;
61
- if (set.length > 1) {
69
+ if (hasParentPortal && set.length > 1) {
62
70
  name = `↳ ${name}`;
63
71
  }
64
72
  } else if (isDeveloperTestAccount(portal)) {
65
73
  name = `${portal.name} [${
66
74
  HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST]
67
75
  }]`;
68
- if (set.length > 1) {
76
+ if (hasParentPortal && set.length > 1) {
69
77
  name = `↳ ${name}`;
70
78
  }
79
+ } else if (isAppDeveloperAccount(portal)) {
80
+ name = `${portal.name} [${
81
+ HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER]
82
+ }]`;
71
83
  }
72
84
  portalData.push([name, portal.portalId, portal.authType]);
73
85
  });
package/commands/init.js CHANGED
@@ -62,7 +62,7 @@ const personalAccessKeyConfigCreationFlow = async (env, account) => {
62
62
 
63
63
  try {
64
64
  const token = await getAccessToken(personalAccessKey, env);
65
- const defaultName = toKebabCase(token.hubName);
65
+ const defaultName = token.hubName ? toKebabCase(token.hubName) : null;
66
66
  const { name } = await enterAccountNamePrompt(defaultName);
67
67
 
68
68
  updatedConfig = updateConfigWithAccessToken(
@@ -5,10 +5,7 @@ const {
5
5
  addUseEnvironmentOptions,
6
6
  addTestingOptions,
7
7
  } = require('../../lib/commonOpts');
8
- const {
9
- trackCommandUsage,
10
- trackCommandMetadataUsage,
11
- } = require('../../lib/usageTracking');
8
+ const { trackCommandUsage } = require('../../lib/usageTracking');
12
9
  const { loadAndValidateOptions } = require('../../lib/validation');
13
10
  const { handleExit } = require('../../lib/process');
14
11
  const { i18n } = require('../../lib/lang');
@@ -18,61 +15,40 @@ const {
18
15
  getAccountConfig,
19
16
  getEnv,
20
17
  } = require('@hubspot/local-dev-lib/config');
21
- const {
22
- createProject,
23
- fetchProject,
24
- } = require('@hubspot/local-dev-lib/api/projects');
18
+ const { fetchProject } = require('@hubspot/local-dev-lib/api/projects');
25
19
  const {
26
20
  getProjectConfig,
27
21
  ensureProjectExists,
28
- handleProjectUpload,
29
- pollProjectBuildAndDeploy,
30
22
  validateProjectConfig,
31
23
  } = require('../../lib/projects');
32
24
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
33
- const {
34
- uiAccountDescription,
35
- uiBetaTag,
36
- uiCommandReference,
37
- uiLine,
38
- } = require('../../lib/ui');
39
- const { confirmPrompt } = require('../../lib/prompts/promptUtils');
40
- const {
41
- selectTargetAccountPrompt,
42
- confirmDefaultAccountPrompt,
43
- } = require('../../lib/prompts/projectDevTargetAccountPrompt');
25
+ const { uiBetaTag } = require('../../lib/ui');
44
26
  const SpinniesManager = require('../../lib/ui/SpinniesManager');
45
27
  const LocalDevManager = require('../../lib/LocalDevManager');
46
- const { isSandbox, getSandboxTypeAsString } = require('../../lib/sandboxes');
47
- const { sandboxNamePrompt } = require('../../lib/prompts/sandboxesPrompt');
48
28
  const {
49
- validateSandboxUsageLimits,
50
- getAvailableSyncTypes,
51
- } = require('../../lib/sandboxes');
29
+ isSandbox,
30
+ isDeveloperTestAccount,
31
+ isStandardAccount,
32
+ isAppDeveloperAccount,
33
+ } = require('../../lib/accountTypes');
52
34
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
35
+
53
36
  const {
54
- PROJECT_BUILD_TEXT,
55
- PROJECT_DEPLOY_TEXT,
56
- PROJECT_ERROR_TYPES,
57
- } = require('../../lib/constants');
58
-
59
- const { buildSandbox } = require('../../lib/sandboxCreate');
60
- const { syncSandbox } = require('../../lib/sandboxSync');
61
- const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
62
- const {
63
- logApiErrorInstance,
64
- ApiErrorContext,
65
- } = require('../../lib/errorHandlers/apiErrors');
66
- const {
67
- isMissingScopeError,
68
- isSpecifiedError,
69
- } = require('@hubspot/local-dev-lib/errors/apiErrors');
70
- const { logErrorInstance } = require('../../lib/errorHandlers/standardErrors');
71
- const { isDeveloperTestAccount } = require('../../lib/developerTestAccounts');
37
+ findProjectComponents,
38
+ getProjectComponentTypes,
39
+ COMPONENT_TYPES,
40
+ } = require('../../lib/projectStructure');
72
41
  const {
73
- HUBSPOT_ACCOUNT_TYPES,
74
- HUBSPOT_ACCOUNT_TYPE_STRINGS,
75
- } = require('@hubspot/local-dev-lib/constants/config');
42
+ confirmDefaultAccountIsTarget,
43
+ suggestRecommendedNestedAccount,
44
+ checkIfAppDeveloperAccount,
45
+ checkIfDeveloperTestAccount,
46
+ createSandboxForLocalDev,
47
+ createDeveloperTestAccountForLocalDev,
48
+ createNewProjectForLocalDev,
49
+ createInitialBuildForNewProject,
50
+ useExistingDevTestAccount,
51
+ } = require('../../lib/localDev');
76
52
 
77
53
  const i18nKey = 'cli.commands.project.subcommands.dev';
78
54
 
@@ -98,126 +74,91 @@ exports.handler = async options => {
98
74
 
99
75
  validateProjectConfig(projectConfig, projectDir);
100
76
 
77
+ const components = await findProjectComponents(projectDir);
78
+ const componentTypes = getProjectComponentTypes(components);
79
+ const hasPrivateApps = componentTypes[COMPONENT_TYPES.privateApp];
80
+ const hasPublicApps = componentTypes[COMPONENT_TYPES.publicApp];
81
+
82
+ if (hasPrivateApps && hasPublicApps) {
83
+ logger.error(i18n(`${i18nKey}.errors.invalidProjectComponents`));
84
+ process.exit(EXIT_CODES.SUCCESS);
85
+ }
86
+
101
87
  const accounts = getConfigAccounts();
102
- let targetAccountId = options.account ? accountId : null;
103
- let createNewSandbox = false;
104
- const defaultAccountIsSandbox = isSandbox(accountConfig);
105
- const defaultAccountIsDeveloperTestAccount = isDeveloperTestAccount(
106
- accountConfig
107
- );
108
88
 
109
- if (
110
- !targetAccountId &&
111
- (defaultAccountIsSandbox || defaultAccountIsDeveloperTestAccount)
112
- ) {
113
- logger.log();
114
- const useDefaultAccount = await confirmDefaultAccountPrompt(
115
- accountConfig.name,
116
- defaultAccountIsSandbox
117
- ? `${getSandboxTypeAsString(accountConfig.accountType)} sandbox`
118
- : HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST]
119
- );
89
+ const defaultAccountIsRecommendedType =
90
+ isDeveloperTestAccount(accountConfig) ||
91
+ (!hasPublicApps && isSandbox(accountConfig));
120
92
 
121
- if (useDefaultAccount) {
122
- targetAccountId = accountId;
123
- } else {
124
- logger.log(
125
- i18n(`${i18nKey}.logs.declineDefaultAccountExplanation`, {
126
- useCommand: uiCommandReference('hs accounts use'),
127
- devCommand: uiCommandReference('hs project dev'),
128
- })
129
- );
130
- process.exit(EXIT_CODES.SUCCESS);
131
- }
93
+ // The account that the project must exist in
94
+ let targetProjectAccountId = options.account ? accountId : null;
95
+ // The account that we are locally testing against
96
+ let targetTestingAccountId = options.account ? accountId : null;
97
+
98
+ if (options.account && hasPublicApps) {
99
+ checkIfDeveloperTestAccount(accountConfig);
100
+ targetProjectAccountId = accountConfig.parentAccountId;
101
+ targetTestingAccountId = accountId;
132
102
  }
133
103
 
134
- if (!targetAccountId) {
135
- logger.log();
136
- uiLine();
137
- logger.warn(i18n(`${i18nKey}.logs.nonSandboxWarning`));
138
- uiLine();
139
- logger.log();
104
+ let createNewSandbox = false;
105
+ let createNewDeveloperTestAccount = false;
106
+
107
+ if (!targetProjectAccountId && defaultAccountIsRecommendedType) {
108
+ await confirmDefaultAccountIsTarget(accountConfig, hasPublicApps);
109
+ targetProjectAccountId = hasPublicApps
110
+ ? accountConfig.parentAccountId
111
+ : accountId;
112
+ targetTestingAccountId = accountId;
113
+ } else if (!targetProjectAccountId && hasPublicApps) {
114
+ checkIfAppDeveloperAccount(accountConfig);
115
+ }
140
116
 
117
+ if (!targetProjectAccountId) {
141
118
  const {
142
- targetAccountId: promptTargetAccountId,
143
- createNewSandbox: promptCreateNewSandbox,
144
- } = await selectTargetAccountPrompt(accounts, accountConfig);
119
+ targetAccountId,
120
+ parentAccountId,
121
+ createNestedAccount,
122
+ notInConfigAccount,
123
+ } = await suggestRecommendedNestedAccount(
124
+ accounts,
125
+ accountConfig,
126
+ hasPublicApps
127
+ );
128
+
129
+ targetProjectAccountId = hasPublicApps ? parentAccountId : targetAccountId;
130
+ targetTestingAccountId = targetAccountId;
145
131
 
146
- targetAccountId = promptTargetAccountId;
147
- createNewSandbox = promptCreateNewSandbox;
132
+ // Only used for developer test accounts that are not yet in the config
133
+ if (notInConfigAccount) {
134
+ await useExistingDevTestAccount(env, notInConfigAccount);
135
+ }
136
+
137
+ createNewSandbox = isStandardAccount(accountConfig) && createNestedAccount;
138
+ createNewDeveloperTestAccount =
139
+ isAppDeveloperAccount(accountConfig) && createNestedAccount;
148
140
  }
149
141
 
150
142
  if (createNewSandbox) {
151
- try {
152
- await validateSandboxUsageLimits(
153
- accountConfig,
154
- HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
155
- env
156
- );
157
- } catch (err) {
158
- if (isMissingScopeError(err)) {
159
- logger.error(
160
- i18n('cli.lib.sandbox.create.failure.scopes.message', {
161
- accountName: accountConfig.name || accountId,
162
- })
163
- );
164
- const websiteOrigin = getHubSpotWebsiteOrigin(env);
165
- const url = `${websiteOrigin}/personal-access-key/${accountId}`;
166
- logger.info(
167
- i18n('cli.lib.sandbox.create.failure.scopes.instructions', {
168
- accountName: accountConfig.name || accountId,
169
- url,
170
- })
171
- );
172
- } else {
173
- logErrorInstance(err);
174
- }
175
- process.exit(EXIT_CODES.ERROR);
176
- }
177
- try {
178
- const { name } = await sandboxNamePrompt(
179
- HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX
180
- );
181
-
182
- trackCommandMetadataUsage(
183
- 'sandbox-create',
184
- { step: 'project-dev' },
185
- accountId
186
- );
187
-
188
- const { result } = await buildSandbox({
189
- name,
190
- type: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
191
- accountConfig,
192
- env,
193
- });
194
-
195
- targetAccountId = result.sandbox.sandboxHubId;
196
-
197
- const sandboxAccountConfig = getAccountConfig(
198
- result.sandbox.sandboxHubId
199
- );
200
- const syncTasks = await getAvailableSyncTypes(
201
- accountConfig,
202
- sandboxAccountConfig
203
- );
204
- await syncSandbox({
205
- accountConfig: sandboxAccountConfig,
206
- parentAccountConfig: accountConfig,
207
- env,
208
- syncTasks,
209
- allowEarlyTermination: false, // Don't let user terminate early in this flow
210
- skipPolling: true, // Skip polling, sync will run and complete in the background
211
- });
212
- } catch (err) {
213
- logErrorInstance(err);
214
- process.exit(EXIT_CODES.ERROR);
215
- }
143
+ targetProjectAccountId = await createSandboxForLocalDev(
144
+ accountId,
145
+ accountConfig,
146
+ env
147
+ );
148
+ // We will be running our tests against this new sandbox account
149
+ targetTestingAccountId = targetProjectAccountId;
150
+ }
151
+ if (createNewDeveloperTestAccount) {
152
+ targetTestingAccountId = await createDeveloperTestAccountForLocalDev(
153
+ accountId,
154
+ accountConfig,
155
+ env
156
+ );
157
+ targetProjectAccountId = accountId;
216
158
  }
217
159
 
218
- logger.log();
219
160
  const projectExists = await ensureProjectExists(
220
- targetAccountId,
161
+ targetProjectAccountId,
221
162
  projectConfig.name,
222
163
  {
223
164
  allowCreate: false,
@@ -230,7 +171,10 @@ exports.handler = async options => {
230
171
  let isGithubLinked;
231
172
 
232
173
  if (projectExists) {
233
- const project = await fetchProject(targetAccountId, projectConfig.name);
174
+ const project = await fetchProject(
175
+ targetProjectAccountId,
176
+ projectConfig.name
177
+ );
234
178
  deployedBuild = project.deployedBuild;
235
179
  isGithubLinked =
236
180
  project.sourceIntegration &&
@@ -240,114 +184,18 @@ exports.handler = async options => {
240
184
  SpinniesManager.init();
241
185
 
242
186
  if (!projectExists) {
243
- // Create the project without prompting if this is a newly created sandbox
244
- let shouldCreateProject = createNewSandbox;
245
-
246
- if (!shouldCreateProject) {
247
- logger.log();
248
- uiLine();
249
- logger.warn(
250
- i18n(`${i18nKey}.logs.projectMustExistExplanation`, {
251
- accountIdentifier: uiAccountDescription(targetAccountId),
252
- projectName: projectConfig.name,
253
- })
254
- );
255
- uiLine();
256
-
257
- shouldCreateProject = await confirmPrompt(
258
- i18n(`${i18nKey}.prompt.createProject`, {
259
- accountIdentifier: uiAccountDescription(targetAccountId),
260
- projectName: projectConfig.name,
261
- })
262
- );
263
- }
264
-
265
- if (shouldCreateProject) {
266
- SpinniesManager.add('createProject', {
267
- text: i18n(`${i18nKey}.status.creatingProject`, {
268
- accountIdentifier: uiAccountDescription(targetAccountId),
269
- projectName: projectConfig.name,
270
- }),
271
- });
272
-
273
- try {
274
- await createProject(targetAccountId, projectConfig.name);
275
- SpinniesManager.succeed('createProject', {
276
- text: i18n(`${i18nKey}.status.createdProject`, {
277
- accountIdentifier: uiAccountDescription(targetAccountId),
278
- projectName: projectConfig.name,
279
- }),
280
- succeedColor: 'white',
281
- });
282
- } catch (err) {
283
- SpinniesManager.fail('createProject');
284
- logger.log(i18n(`${i18nKey}.status.failedToCreateProject`));
285
- process.exit(EXIT_CODES.ERROR);
286
- }
287
- } else {
288
- // We cannot continue if the project does not exist in the target account
289
- logger.log();
290
- logger.log(i18n(`${i18nKey}.logs.choseNotToCreateProject`));
291
- process.exit(EXIT_CODES.SUCCESS);
292
- }
293
- }
294
-
295
- let initialUploadResult;
187
+ await createNewProjectForLocalDev(
188
+ projectConfig,
189
+ targetProjectAccountId,
190
+ createNewSandbox,
191
+ hasPublicApps
192
+ );
296
193
 
297
- // Create an initial build if the project was newly created in the account
298
- if (!projectExists) {
299
- initialUploadResult = await handleProjectUpload(
300
- targetAccountId,
194
+ deployedBuild = await createInitialBuildForNewProject(
301
195
  projectConfig,
302
196
  projectDir,
303
- (...args) => pollProjectBuildAndDeploy(...args, true),
304
- i18n(`${i18nKey}.logs.initialUploadMessage`)
197
+ targetProjectAccountId
305
198
  );
306
-
307
- if (initialUploadResult.uploadError) {
308
- if (
309
- isSpecifiedError(initialUploadResult.uploadError, {
310
- subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED,
311
- })
312
- ) {
313
- logger.log();
314
- logger.error(i18n(`${i18nKey}.errors.projectLockedError`));
315
- logger.log();
316
- } else {
317
- logApiErrorInstance(
318
- initialUploadResult.uploadError,
319
- new ApiErrorContext({
320
- accountId,
321
- projectName: projectConfig.name,
322
- })
323
- );
324
- }
325
- process.exit(EXIT_CODES.ERROR);
326
- }
327
-
328
- if (!initialUploadResult.succeeded) {
329
- let subTasks = [];
330
-
331
- if (initialUploadResult.buildResult.status === 'FAILURE') {
332
- subTasks =
333
- initialUploadResult.buildResult[PROJECT_BUILD_TEXT.SUBTASK_KEY];
334
- } else if (initialUploadResult.deployResult.status === 'FAILURE') {
335
- subTasks =
336
- initialUploadResult.deployResult[PROJECT_DEPLOY_TEXT.SUBTASK_KEY];
337
- }
338
-
339
- const failedSubTasks = subTasks.filter(task => task.status === 'FAILURE');
340
-
341
- logger.log();
342
- failedSubTasks.forEach(failedSubTask => {
343
- console.error(failedSubTask.errorMessage);
344
- });
345
- logger.log();
346
-
347
- process.exit(EXIT_CODES.ERROR);
348
- }
349
-
350
- deployedBuild = initialUploadResult.buildResult;
351
199
  }
352
200
 
353
201
  const LocalDev = new LocalDevManager({
@@ -355,8 +203,9 @@ exports.handler = async options => {
355
203
  deployedBuild,
356
204
  projectConfig,
357
205
  projectDir,
358
- targetAccountId,
206
+ targetAccountId: targetTestingAccountId,
359
207
  isGithubLinked,
208
+ components,
360
209
  });
361
210
 
362
211
  await LocalDev.start();
@@ -16,6 +16,7 @@ const {
16
16
  logFeedbackMessage,
17
17
  validateProjectConfig,
18
18
  pollProjectBuildAndDeploy,
19
+ displayWarnLogs,
19
20
  } = require('../../lib/projects');
20
21
  const { i18n } = require('../../lib/lang');
21
22
  const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
@@ -91,6 +92,8 @@ exports.handler = async options => {
91
92
  );
92
93
  uiLine();
93
94
  logFeedbackMessage(result.buildId);
95
+
96
+ displayWarnLogs(accountId, projectConfig.name, result.buildId);
94
97
  process.exit(EXIT_CODES.SUCCESS);
95
98
  }
96
99
  } catch (e) {
@@ -14,12 +14,14 @@ const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config');
14
14
  const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
15
15
  const { promptUser } = require('../../lib/prompts/promptUtils');
16
16
  const { uiLine, uiAccountDescription } = require('../../lib/ui');
17
+ const {
18
+ isSandbox,
19
+ isStandardSandbox,
20
+ isDevelopmentSandbox,
21
+ } = require('../../lib/accountTypes');
17
22
  const {
18
23
  getAvailableSyncTypes,
19
24
  getSyncTypesWithContactRecordsPrompt,
20
- isDevelopmentSandbox,
21
- isStandardSandbox,
22
- isSandbox,
23
25
  } = require('../../lib/sandboxes');
24
26
  const { syncSandbox } = require('../../lib/sandboxSync');
25
27
  const { getValidEnv } = require('@hubspot/local-dev-lib/environment');
@@ -12,19 +12,28 @@ const { preview } = require('@hubspot/theme-preview-dev-server');
12
12
  const { getUploadableFileList } = require('../../lib/upload');
13
13
  const { trackCommandUsage } = require('../../lib/usageTracking');
14
14
  const { loadAndValidateOptions } = require('../../lib/validation');
15
- const { previewPrompt } = require('../../lib/prompts/previewPrompt');
15
+ const {
16
+ previewPrompt,
17
+ previewProjectPrompt,
18
+ } = require('../../lib/prompts/previewPrompt');
16
19
  const { EXIT_CODES } = require('../../lib/enums/exitCodes');
17
20
  const {
18
21
  FILE_UPLOAD_RESULT_TYPES,
19
22
  } = require('@hubspot/local-dev-lib/constants/files');
20
- const i18nKey = 'cli.commands.preview';
21
23
  const cliProgress = require('cli-progress');
22
24
  const {
23
25
  ApiErrorContext,
24
26
  logApiUploadErrorInstance,
25
27
  } = require('../../lib/errorHandlers/apiErrors');
26
28
  const { handleExit, handleKeypress } = require('../../lib/process');
29
+ const { getThemeJSONPath } = require('@hubspot/local-dev-lib/cms/themes');
30
+ const { getProjectConfig } = require('../../lib/projects');
31
+ const {
32
+ findProjectComponents,
33
+ COMPONENT_TYPES,
34
+ } = require('../../lib/projectStructure');
27
35
 
36
+ const i18nKey = 'cli.commands.preview';
28
37
  exports.command = 'preview [--src] [--dest]';
29
38
  exports.describe = false; // i18n(`${i18nKey}.describe`) - Hiding command
30
39
 
@@ -63,6 +72,42 @@ const handleUserInput = () => {
63
72
  });
64
73
  };
65
74
 
75
+ const determineSrcAndDest = async options => {
76
+ let absoluteSrc;
77
+ let dest;
78
+ const { projectDir, projectConfig } = await getProjectConfig();
79
+ if (!(projectDir && projectConfig)) {
80
+ // Not in a project, prompt for src and dest of traditional theme
81
+ const previewPromptAnswers = await previewPrompt(options);
82
+ const src = options.src || previewPromptAnswers.src;
83
+ dest = options.dest || previewPromptAnswers.dest;
84
+ absoluteSrc = path.resolve(getCwd(), src);
85
+ if (!dest || !validateSrcPath(absoluteSrc)) {
86
+ process.exit(EXIT_CODES.ERROR);
87
+ }
88
+ } else {
89
+ // In a project
90
+ let themeJsonPath = getThemeJSONPath();
91
+ if (!themeJsonPath) {
92
+ const projectComponents = await findProjectComponents(projectDir);
93
+ const themeComponents = projectComponents.filter(
94
+ c => c.type === COMPONENT_TYPES.hublTheme
95
+ );
96
+ if (themeComponents.length === 0) {
97
+ logger.error(i18n(`${i18nKey}.errors.noThemeComponents`));
98
+ process.exit(EXIT_CODES.ERROR);
99
+ }
100
+ const answer = await previewProjectPrompt(themeComponents);
101
+ themeJsonPath = `${answer.themeComponentPath}/theme.json`;
102
+ }
103
+ const { dir: themeDir } = path.parse(themeJsonPath);
104
+ absoluteSrc = themeDir;
105
+ const { base: themeName } = path.parse(themeDir);
106
+ dest = `@projects/${projectConfig.name}/${themeName}`;
107
+ }
108
+ return { absoluteSrc, dest };
109
+ };
110
+
66
111
  exports.handler = async options => {
67
112
  const { notify, skipUpload, noSsl, port, debug } = options;
68
113
 
@@ -70,18 +115,7 @@ exports.handler = async options => {
70
115
 
71
116
  const accountId = getAccountId(options);
72
117
 
73
- const previewPromptAnswers = await previewPrompt(options);
74
- const src = options.src || previewPromptAnswers.src;
75
- let dest = options.dest || previewPromptAnswers.dest;
76
- if (!dest) {
77
- logger.error(i18n(`${i18nKey}.errors.destinationRequired`));
78
- return;
79
- }
80
-
81
- const absoluteSrc = path.resolve(getCwd(), src);
82
- if (!validateSrcPath(absoluteSrc)) {
83
- process.exit(EXIT_CODES.ERROR);
84
- }
118
+ const { absoluteSrc, dest } = await determineSrcAndDest(options);
85
119
 
86
120
  const filePaths = await getUploadableFileList(absoluteSrc, false);
87
121