@hubspot/cli 8.14.0-beta.1 → 8.15.0-beta.0

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.
Files changed (91) hide show
  1. package/commands/cms/theme/preview.js +8 -1
  2. package/commands/getStarted.js +15 -20
  3. package/commands/project/add.js +7 -6
  4. package/commands/project/appInstallStatus.d.ts +1 -1
  5. package/commands/project/appInstallStatus.js +8 -4
  6. package/commands/project/create.js +20 -6
  7. package/commands/project/delete.js +1 -1
  8. package/commands/project/deploy.d.ts +1 -1
  9. package/commands/project/deploy.js +14 -12
  10. package/commands/project/dev/index.js +4 -7
  11. package/commands/project/download.js +3 -3
  12. package/commands/project/info.d.ts +1 -1
  13. package/commands/project/info.js +8 -5
  14. package/commands/project/installApp.d.ts +1 -1
  15. package/commands/project/installApp.js +9 -4
  16. package/commands/project/installDeps.js +6 -5
  17. package/commands/project/lint.js +16 -9
  18. package/commands/project/list.d.ts +3 -2
  19. package/commands/project/list.js +21 -3
  20. package/commands/project/listBuilds.d.ts +1 -1
  21. package/commands/project/listBuilds.js +4 -8
  22. package/commands/project/migrate.js +14 -12
  23. package/commands/project/open.js +15 -7
  24. package/commands/project/profile/add.js +8 -4
  25. package/commands/project/profile/delete.js +8 -4
  26. package/commands/project/release/create.d.ts +1 -1
  27. package/commands/project/release/create.js +5 -7
  28. package/commands/project/release/info.d.ts +1 -1
  29. package/commands/project/release/info.js +4 -4
  30. package/commands/project/release/list.d.ts +1 -1
  31. package/commands/project/release/list.js +4 -4
  32. package/commands/project/updateDeps.js +6 -5
  33. package/commands/project/upload.d.ts +1 -1
  34. package/commands/project/upload.js +26 -15
  35. package/commands/project/validate.js +14 -97
  36. package/commands/project/watch.js +8 -11
  37. package/commands/project.js +1 -1
  38. package/commands/testAccount/create.d.ts +1 -1
  39. package/commands/testAccount/create.js +1 -1
  40. package/lang/en.d.ts +7 -18
  41. package/lang/en.js +7 -18
  42. package/lib/app/migrate.js +5 -7
  43. package/lib/dependencyManagement.js +3 -10
  44. package/lib/doctor/DiagnosticInfoBuilder.d.ts +2 -2
  45. package/lib/doctor/DiagnosticInfoBuilder.js +8 -8
  46. package/lib/doctor/Doctor.js +7 -9
  47. package/lib/errors/ProjectErrors.d.ts +0 -6
  48. package/lib/errors/ProjectErrors.js +0 -12
  49. package/lib/errors/ProjectValidationError.d.ts +2 -1
  50. package/lib/errors/ProjectValidationError.js +3 -1
  51. package/lib/getStartedV2Actions.js +14 -18
  52. package/lib/jsonOutput/appInstallStatus.d.ts +14 -0
  53. package/lib/jsonOutput/appInstallStatus.js +14 -0
  54. package/lib/jsonOutput/createTestAccount.d.ts +7 -0
  55. package/lib/jsonOutput/createTestAccount.js +6 -0
  56. package/lib/jsonOutput/deploy.d.ts +5 -0
  57. package/lib/jsonOutput/deploy.js +4 -0
  58. package/lib/jsonOutput/info.d.ts +21 -0
  59. package/lib/jsonOutput/info.js +19 -0
  60. package/lib/jsonOutput/installApp.d.ts +11 -0
  61. package/lib/jsonOutput/installApp.js +10 -0
  62. package/lib/jsonOutput/listBuilds.d.ts +75 -0
  63. package/lib/jsonOutput/listBuilds.js +65 -0
  64. package/lib/jsonOutput/projectList.d.ts +29 -0
  65. package/lib/jsonOutput/projectList.js +28 -0
  66. package/lib/jsonOutput/release.d.ts +42 -0
  67. package/lib/jsonOutput/release.js +37 -0
  68. package/lib/jsonOutput/upload.d.ts +29 -0
  69. package/lib/jsonOutput/upload.js +23 -0
  70. package/lib/npm/packageJson.js +1 -1
  71. package/lib/projects/ProjectLogsManager.js +1 -4
  72. package/lib/projects/config.d.ts +6 -5
  73. package/lib/projects/config.js +7 -38
  74. package/lib/projects/create/tracking.d.ts +1 -0
  75. package/lib/projects/create/tracking.js +27 -0
  76. package/lib/projects/delete.js +14 -1
  77. package/lib/projects/localDev/LocalDevProcess.js +7 -7
  78. package/lib/projects/projectProfiles.d.ts +2 -2
  79. package/lib/projects/projectProfiles.js +0 -3
  80. package/lib/projects/release.js +1 -1
  81. package/lib/projects/uieLinting.js +0 -3
  82. package/lib/projects/validate.d.ts +9 -0
  83. package/lib/projects/validate.js +96 -0
  84. package/lib/theme/migrate.js +1 -4
  85. package/mcp-server/tools/project/GetBuildLogsTool.js +2 -3
  86. package/mcp-server/tools/project/GetBuildStatusTool.js +2 -3
  87. package/mcp-server/tools/project/UploadProjectTools.js +3 -5
  88. package/package.json +3 -3
  89. package/types/Projects.d.ts +1 -5
  90. package/lib/jsonOutput.d.ts +0 -182
  91. package/lib/jsonOutput.js +0 -158
@@ -35,7 +35,14 @@ function validateSrcPath(src) {
35
35
  async function determineSrcAndDest(args) {
36
36
  let absoluteSrc;
37
37
  let dest;
38
- const { projectDir, projectConfig } = await getProjectConfig();
38
+ let projectDir;
39
+ let projectConfig;
40
+ try {
41
+ ({ projectDir, projectConfig } = getProjectConfig());
42
+ }
43
+ catch {
44
+ // Not in a project (handled below)
45
+ }
39
46
  if (!(projectDir && projectConfig)) {
40
47
  // Not in a project, prompt for src and dest of traditional theme
41
48
  const previewPromptAnswers = await previewPrompt(args);
@@ -7,7 +7,7 @@ import { commands } from '../lang/en.js';
7
7
  import { GET_STARTED_OPTIONS, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_CONFIG_FILE, } from '../lib/constants.js';
8
8
  import { EXIT_CODES } from '../lib/enums/exitCodes.js';
9
9
  import { debugError, logError } from '../lib/errorHandlers/index.js';
10
- import { getProjectConfig, validateProjectConfig, writeProjectConfig, } from '../lib/projects/config.js';
10
+ import { getProjectConfig, writeProjectConfig, ProjectConfigValidationError, } from '../lib/projects/config.js';
11
11
  import { handleProjectUpload } from '../lib/projects/upload.js';
12
12
  import { projectNameAndDestPrompt } from '../lib/prompts/projectNameAndDestPrompt.js';
13
13
  import { promptUser } from '../lib/prompts/promptUtils.js';
@@ -20,7 +20,6 @@ import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDe
20
20
  import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev';
21
21
  import { getConfigAccountEnvironment } from '@hubspot/local-dev-lib/config';
22
22
  import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
23
- import ProjectValidationError from '../lib/errors/ProjectValidationError.js';
24
23
  import { openLink } from '../lib/links.js';
25
24
  import { runGetStartedV2 } from '../lib/getStarted/getStartedV2.js';
26
25
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
@@ -98,10 +97,14 @@ async function handler(args) {
98
97
  uiLogger.log(commands.getStarted.logs.appSelected);
99
98
  const { dest, name } = await projectNameAndDestPrompt(args);
100
99
  const projectDest = path.resolve(getCwd(), dest);
101
- const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
102
- if (existingProjectConfig &&
103
- existingProjectDir &&
104
- projectDest.startsWith(existingProjectDir)) {
100
+ let existingProjectDir;
101
+ try {
102
+ existingProjectDir = getProjectConfig(projectDest).projectDir;
103
+ }
104
+ catch {
105
+ // Not in a project directory
106
+ }
107
+ if (existingProjectDir && projectDest.startsWith(existingProjectDir)) {
105
108
  // Track nested project error
106
109
  await trackCommandMetadataUsage('get-started', {
107
110
  successful: false,
@@ -167,29 +170,21 @@ async function handler(args) {
167
170
  if (shouldUpload) {
168
171
  try {
169
172
  // Get the project config for the newly created project
170
- const { projectConfig: newProjectConfig, projectDir: newProjectDir } = await getProjectConfig(projectDest);
171
- if (!newProjectConfig || !newProjectDir) {
172
- // Track config file not found error
173
- await trackCommandMetadataUsage('get-started', {
174
- successful: false,
175
- step: 'config-file-not-found',
176
- }, derivedAccountId);
177
- uiLogger.log(' ');
178
- uiLogger.error(commands.getStarted.errors.configFileNotFound);
179
- return exit(EXIT_CODES.ERROR);
180
- }
173
+ let newProjectConfig;
174
+ let newProjectDir;
181
175
  try {
182
- validateProjectConfig(newProjectConfig, newProjectDir);
176
+ ({ projectConfig: newProjectConfig, projectDir: newProjectDir } =
177
+ getProjectConfig(projectDest));
183
178
  }
184
179
  catch (error) {
185
- if (error instanceof ProjectValidationError) {
180
+ if (error instanceof ProjectConfigValidationError) {
186
181
  // Track validation error
187
182
  await trackCommandMetadataUsage('get-started', {
188
183
  successful: false,
189
184
  step: 'project-validation-failed',
190
185
  }, derivedAccountId);
191
186
  uiLogger.log(' ');
192
- uiLogger.error(error.message);
187
+ uiLogger.error(error.message || commands.getStarted.errors.configFileNotFound);
193
188
  return exit(EXIT_CODES.ERROR);
194
189
  }
195
190
  throw error;
@@ -1,5 +1,5 @@
1
1
  import { logError } from '../../lib/errorHandlers/index.js';
2
- import { getProjectConfig } from '../../lib/projects/config.js';
2
+ import { getIsInProject, getProjectConfig } from '../../lib/projects/config.js';
3
3
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
4
4
  import { isPromptExitError } from '../../lib/errors/PromptExitError.js';
5
5
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
@@ -14,12 +14,13 @@ const command = 'add';
14
14
  const describe = commands.project.add.describe;
15
15
  async function handler(args) {
16
16
  const { derivedAccountId, exit } = args;
17
+ const isInProjectDir = getIsInProject();
18
+ if (!isInProjectDir) {
19
+ uiLogger.error(commands.project.add.error.locationInProject);
20
+ return exit(EXIT_CODES.ERROR);
21
+ }
17
22
  try {
18
- const { projectConfig, projectDir } = await getProjectConfig();
19
- if (!projectDir || !projectConfig) {
20
- uiLogger.error(commands.project.add.error.locationInProject);
21
- return exit(EXIT_CODES.ERROR);
22
- }
23
+ const { projectConfig, projectDir } = getProjectConfig();
23
24
  const isLegacyProjectCreate = isLegacyProject(projectConfig.platformVersion);
24
25
  if (!isLegacyProjectCreate) {
25
26
  await v2AddComponent(args, projectDir, projectConfig, derivedAccountId);
@@ -1,5 +1,5 @@
1
1
  import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- import { InstallStatusJsonOutput } from '../../lib/jsonOutput.js';
2
+ import { InstallStatusJsonOutput } from '../../lib/jsonOutput/appInstallStatus.js';
3
3
  export type ProjectInstallStatusArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<InstallStatusJsonOutput>;
4
4
  declare const projectInstallStatusCommand: YargsCommandModule<unknown, ProjectInstallStatusArgs>;
5
5
  export default projectInstallStatusCommand;
@@ -5,7 +5,7 @@ import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
5
5
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
6
6
  import { translateForLocalDev } from '@hubspot/project-parsing-lib/translate';
7
7
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
8
- import { InstallStatusSchema, } from '../../lib/jsonOutput.js';
8
+ import { InstallStatusSchema, } from '../../lib/jsonOutput/appInstallStatus.js';
9
9
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
10
10
  import { uiLogger } from '../../lib/ui/logger.js';
11
11
  import { ApiErrorContext, debugError, logError, } from '../../lib/errorHandlers/index.js';
@@ -18,9 +18,13 @@ const command = 'app-install-status';
18
18
  const describe = commands.project.installStatus.describe;
19
19
  async function handler(args) {
20
20
  const { derivedAccountId, formatOutputAsJson, exit, addJsonOutput } = args;
21
- const { projectConfig, projectDir } = await getProjectConfig();
22
- if (!projectConfig || !projectDir) {
23
- uiLogger.error(commands.project.installStatus.errors.noProjectConfig);
21
+ let projectConfig;
22
+ let projectDir;
23
+ try {
24
+ ({ projectConfig, projectDir } = getProjectConfig());
25
+ }
26
+ catch (error) {
27
+ logError(error);
24
28
  return exit(EXIT_CODES.ERROR);
25
29
  }
26
30
  if (isLegacyProject(projectConfig.platformVersion)) {
@@ -15,6 +15,7 @@ import { makeYargsBuilder } from '../../lib/yargsUtils.js';
15
15
  import { commands } from '../../lang/en.js';
16
16
  import { uiLogger } from '../../lib/ui/logger.js';
17
17
  import { handleProjectCreationFlow, } from '../../lib/projects/create/index.js';
18
+ import { getProjectCreateFailureReason } from '../../lib/projects/create/tracking.js';
18
19
  import { updateHsMetaFilesWithAutoGeneratedFields } from '../../lib/projects/components.js';
19
20
  import SpinniesManager from '../../lib/ui/SpinniesManager.js';
20
21
  import { showMcpPromotionNudge } from '../../lib/mcp/promotion.js';
@@ -24,6 +25,10 @@ const BETA_VERSIONS = [
24
25
  PLATFORM_VERSIONS.v2026_09_BETA,
25
26
  PLATFORM_VERSIONS.v2026_03_BETA,
26
27
  ];
28
+ const TRACKING_STEP = {
29
+ CREATION_FLOW: 'project-creation-flow',
30
+ DOWNLOAD: 'download-project',
31
+ };
27
32
  async function handler(args) {
28
33
  const { platformVersion, templateSource, exit, addUsageMetadata } = args;
29
34
  if (BETA_VERSIONS.includes(platformVersion)) {
@@ -39,6 +44,9 @@ async function handler(args) {
39
44
  handleResult = await handleProjectCreationFlow(args);
40
45
  }
41
46
  catch (error) {
47
+ addUsageMetadata({
48
+ step: `${TRACKING_STEP.CREATION_FLOW}:${getProjectCreateFailureReason(error)}`,
49
+ });
42
50
  logError(error);
43
51
  return exit(EXIT_CODES.ERROR);
44
52
  }
@@ -51,13 +59,16 @@ async function handler(args) {
51
59
  .join(','),
52
60
  });
53
61
  const projectDest = path.resolve(getCwd(), projectNameAndDestPromptResponse.dest);
54
- const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
55
62
  // Exit if the target destination is within an existing project
56
- if (existingProjectConfig &&
57
- existingProjectDir &&
58
- projectDest.startsWith(existingProjectDir)) {
59
- uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
60
- return exit(EXIT_CODES.ERROR);
63
+ try {
64
+ const { projectDir: existingProjectDir } = getProjectConfig(projectDest);
65
+ if (projectDest.startsWith(existingProjectDir)) {
66
+ uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
67
+ return exit(EXIT_CODES.ERROR);
68
+ }
69
+ }
70
+ catch {
71
+ // Not in a project directory, which is the expected case for create
61
72
  }
62
73
  const components = generateComponentPaths({
63
74
  selectProjectTemplatePromptResponse,
@@ -80,6 +91,9 @@ async function handler(args) {
80
91
  });
81
92
  }
82
93
  catch (err) {
94
+ addUsageMetadata({
95
+ step: `${TRACKING_STEP.DOWNLOAD}:${getProjectCreateFailureReason(err)}`,
96
+ });
83
97
  SpinniesManager.fail('project-create', {
84
98
  text: commands.project.create.failure(isProjectEmpty, projectNameAndDestPromptResponse.name),
85
99
  });
@@ -23,7 +23,7 @@ async function handler(args) {
23
23
  if (!force) {
24
24
  await confirmDeletion(projectName, derivedAccountId, projectId);
25
25
  }
26
- if (!isLegacyProject(platformVersion) && hasUnifiedComponents) {
26
+ if (hasUnifiedComponents && !isLegacyProject(platformVersion)) {
27
27
  await deleteDeployedComponents(derivedAccountId, projectName);
28
28
  }
29
29
  await handleProjectDeletion(derivedAccountId, projectName);
@@ -1,5 +1,5 @@
1
1
  import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- import { DeployJsonOutput } from '../../lib/jsonOutput.js';
2
+ import { DeployJsonOutput } from '../../lib/jsonOutput/deploy.js';
3
3
  export type ProjectDeployArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<DeployJsonOutput> & {
4
4
  project?: string;
5
5
  build?: number;
@@ -3,14 +3,14 @@ import { getConfigAccountById } from '@hubspot/local-dev-lib/config';
3
3
  import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index';
4
4
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
5
5
  import { logError, ApiErrorContext } from '../../lib/errorHandlers/index.js';
6
- import { getProjectConfig, validateProjectConfig, } from '../../lib/projects/config.js';
6
+ import { getProjectConfig } from '../../lib/projects/config.js';
7
7
  import { projectNamePrompt } from '../../lib/prompts/projectNamePrompt.js';
8
8
  import { projectProfilePrompt } from '../../lib/prompts/projectProfilePrompt.js';
9
9
  import { promptUser } from '../../lib/prompts/promptUtils.js';
10
10
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
11
11
  import { uiLogger } from '../../lib/ui/logger.js';
12
12
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
13
- import { DeploySchema } from '../../lib/jsonOutput.js';
13
+ import { DeploySchema } from '../../lib/jsonOutput/deploy.js';
14
14
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
15
15
  import { loadProfile } from '../../lib/projects/projectProfiles.js';
16
16
  import { PROJECT_DEPLOY_TEXT } from '../../lib/constants.js';
@@ -25,22 +25,24 @@ async function handler(args) {
25
25
  const accountConfig = getConfigAccountById(derivedAccountId);
26
26
  const accountType = accountConfig && accountConfig.accountType;
27
27
  let targetAccountId;
28
- const { projectConfig, projectDir } = await getProjectConfig();
28
+ let loadedConfig;
29
29
  let isInProjectDirectory = false;
30
- // Validate project config, but it's valid to run this command from outside a project dir
31
30
  try {
32
- validateProjectConfig(projectConfig, projectDir);
31
+ loadedConfig = getProjectConfig();
33
32
  isInProjectDirectory = true;
34
33
  }
35
- catch (e) { }
34
+ catch (e) {
35
+ // Do nothing, it's valid to run this command from outside a project dir
36
+ }
36
37
  if (isInProjectDirectory &&
37
- !isLegacyProject(projectConfig?.platformVersion)) {
38
+ loadedConfig &&
39
+ !isLegacyProject(loadedConfig.projectConfig.platformVersion)) {
38
40
  try {
39
- const profileName = await projectProfilePrompt(projectDir, projectConfig, profileOption, !!useEnvOption);
41
+ const profileName = await projectProfilePrompt(loadedConfig.projectDir, loadedConfig.projectConfig, profileOption, !!useEnvOption);
40
42
  if (profileName) {
41
43
  // Use loadProfile instead of loadAndValidateProfile because the local
42
44
  // profile does not need to be valid to successfully deploy
43
- const profile = loadProfile(projectConfig, projectDir, profileName);
45
+ const profile = loadProfile(loadedConfig.projectConfig, loadedConfig.projectDir, profileName);
44
46
  targetAccountId = profile.accountId;
45
47
  uiLogger.log(commands.project.deploy.profileMessage(profileName, targetAccountId));
46
48
  uiLogger.log('');
@@ -58,8 +60,8 @@ async function handler(args) {
58
60
  addUsageMetadata({ type: accountType });
59
61
  }
60
62
  let projectName = projectOption;
61
- if (!projectOption && projectConfig) {
62
- projectName = projectConfig.name;
63
+ if (!projectOption && loadedConfig) {
64
+ projectName = loadedConfig.projectConfig.name;
63
65
  }
64
66
  const namePromptResponse = await projectNamePrompt(targetAccountId, {
65
67
  project: projectName,
@@ -116,7 +118,7 @@ async function handler(args) {
116
118
  catch {
117
119
  return exit(EXIT_CODES.ERROR);
118
120
  }
119
- const deployResult = await handleProjectDeploy(targetAccountId, projectName, buildIdToDeploy, isLegacyProject(projectConfig?.platformVersion), forceOption);
121
+ const deployResult = await handleProjectDeploy(targetAccountId, projectName, buildIdToDeploy, isLegacyProject(loadedConfig?.projectConfig.platformVersion), forceOption);
120
122
  if (!deployResult) {
121
123
  return exit(EXIT_CODES.ERROR);
122
124
  }
@@ -1,5 +1,5 @@
1
1
  import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
2
- import { getProjectConfig, validateProjectConfig, } from '../../../lib/projects/config.js';
2
+ import { getProjectConfig } from '../../../lib/projects/config.js';
3
3
  import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
4
4
  import { uiLine } from '../../../lib/ui/index.js';
5
5
  import { makeWrappedYargsHandler } from '../../../lib/yargs/makeWrappedYargsHandler.js';
@@ -17,18 +17,15 @@ const command = 'dev';
17
17
  const describe = commands.project.dev.describe;
18
18
  async function handler(args) {
19
19
  const { derivedAccountId, testingAccount, projectAccount, profile: profileOption, exit, addUsageMetadata, } = args;
20
- const { projectConfig, projectDir } = await getProjectConfig();
20
+ let projectConfig;
21
+ let projectDir;
21
22
  try {
22
- validateProjectConfig(projectConfig, projectDir);
23
+ ({ projectConfig, projectDir } = getProjectConfig());
23
24
  }
24
25
  catch (error) {
25
26
  logError(error);
26
27
  return exit(EXIT_CODES.ERROR);
27
28
  }
28
- if (!projectDir) {
29
- uiLogger.error(commands.project.dev.errors.noProjectConfig);
30
- return exit(EXIT_CODES.ERROR);
31
- }
32
29
  if (isLegacyProject(projectConfig.platformVersion)) {
33
30
  uiLogger.error(commands.project.dev.errors.unsupportedPlatformVersion(projectConfig.platformVersion));
34
31
  return exit(EXIT_CODES.ERROR);
@@ -4,7 +4,7 @@ import { extractZipArchive } from '@hubspot/local-dev-lib/archive';
4
4
  import { downloadProject, fetchProjectBuilds, } from '@hubspot/local-dev-lib/api/projects';
5
5
  import { logError, ApiErrorContext } from '../../lib/errorHandlers/index.js';
6
6
  import { isPromptExitError } from '../../lib/errors/PromptExitError.js';
7
- import { getProjectConfig } from '../../lib/projects/config.js';
7
+ import { getIsInProject } from '../../lib/projects/config.js';
8
8
  import { downloadProjectPrompt } from '../../lib/prompts/downloadProjectPrompt.js';
9
9
  import { commands } from '../../lang/en.js';
10
10
  import { uiLogger } from '../../lib/ui/logger.js';
@@ -15,8 +15,8 @@ const command = 'download';
15
15
  const describe = commands.project.download.describe;
16
16
  async function handler(args) {
17
17
  const { dest, build, derivedAccountId, exit } = args;
18
- const { projectConfig } = await getProjectConfig();
19
- if (projectConfig) {
18
+ const isInProjectDir = getIsInProject();
19
+ if (isInProjectDir) {
20
20
  uiLogger.error(commands.project.download.warnings.cannotDownloadWithinProject);
21
21
  return exit(EXIT_CODES.ERROR);
22
22
  }
@@ -1,5 +1,5 @@
1
1
  import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- import { ProjectInfoJsonOutput } from '../../lib/jsonOutput.js';
2
+ import { ProjectInfoJsonOutput } from '../../lib/jsonOutput/info.js';
3
3
  export type ProjectInfoArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<ProjectInfoJsonOutput>;
4
4
  declare const projectInfoCommand: YargsCommandModule<unknown, ProjectInfoArgs>;
5
5
  export default projectInfoCommand;
@@ -1,22 +1,25 @@
1
1
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
2
2
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
3
3
  import { fetchProject } from '@hubspot/local-dev-lib/api/projects';
4
- import { debugError } from '../../lib/errorHandlers/index.js';
4
+ import { debugError, logError } from '../../lib/errorHandlers/index.js';
5
5
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
6
6
  import { uiLogger } from '../../lib/ui/logger.js';
7
7
  import { commands } from '../../lang/en.js';
8
8
  import { getProjectConfig } from '../../lib/projects/config.js';
9
9
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
10
10
  import { getProjectInfo, logProjectInfo, } from '../../lib/projects/projectInfo.js';
11
- import { ProjectInfoSchema, } from '../../lib/jsonOutput.js';
11
+ import { ProjectInfoSchema, } from '../../lib/jsonOutput/info.js';
12
12
  const command = 'info';
13
13
  const describe = commands.project.info.describe;
14
14
  const verboseDescribe = commands.project.info.verboseDescribe;
15
15
  async function handler(args) {
16
16
  const { derivedAccountId, formatOutputAsJson, exit, addJsonOutput } = args;
17
- const { projectConfig } = await getProjectConfig();
18
- if (!projectConfig) {
19
- uiLogger.error(commands.project.info.errors.noProjectConfig);
17
+ let projectConfig;
18
+ try {
19
+ ({ projectConfig } = getProjectConfig());
20
+ }
21
+ catch (error) {
22
+ logError(error);
20
23
  return exit(EXIT_CODES.ERROR);
21
24
  }
22
25
  if (isLegacyProject(projectConfig.platformVersion)) {
@@ -1,5 +1,5 @@
1
1
  import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- import { InstallAppJsonOutput } from '../../lib/jsonOutput.js';
2
+ import { InstallAppJsonOutput } from '../../lib/jsonOutput/installApp.js';
3
3
  export type ProjectInstallAppArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<InstallAppJsonOutput> & {
4
4
  force: boolean;
5
5
  profile?: string;
@@ -1,9 +1,10 @@
1
1
  import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
2
2
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
3
3
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
4
- import { InstallAppSchema, } from '../../lib/jsonOutput.js';
4
+ import { InstallAppSchema, } from '../../lib/jsonOutput/installApp.js';
5
5
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
6
6
  import { uiLogger } from '../../lib/ui/logger.js';
7
+ import { logError } from '../../lib/errorHandlers/index.js';
7
8
  import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
8
9
  import { commands } from '../../lang/en.js';
9
10
  import { getProjectConfig } from '../../lib/projects/config.js';
@@ -15,9 +16,13 @@ const describe = commands.project.installApp.describe;
15
16
  const verboseDescribe = commands.project.installApp.verboseDescribe;
16
17
  async function handler(args) {
17
18
  const { derivedAccountId, formatOutputAsJson, force, profile: profileOption, useEnv: useEnvOption, exit, addJsonOutput, } = args;
18
- const { projectConfig, projectDir } = await getProjectConfig();
19
- if (!projectConfig || !projectDir) {
20
- uiLogger.error(commands.project.installApp.errors.noProjectConfig);
19
+ let projectConfig;
20
+ let projectDir;
21
+ try {
22
+ ({ projectConfig, projectDir } = getProjectConfig());
23
+ }
24
+ catch (error) {
25
+ logError(error);
21
26
  return exit(EXIT_CODES.ERROR);
22
27
  }
23
28
  if (isLegacyProject(projectConfig.platformVersion)) {
@@ -5,7 +5,6 @@ import { getProjectConfig } from '../../lib/projects/config.js';
5
5
  import { promptUser } from '../../lib/prompts/promptUtils.js';
6
6
  import path from 'path';
7
7
  import { commands } from '../../lang/en.js';
8
- import { uiLogger } from '../../lib/ui/logger.js';
9
8
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
10
9
  import { logError } from '../../lib/errorHandlers/index.js';
11
10
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
@@ -15,12 +14,14 @@ const describe = commands.project.installDeps.help.describe;
15
14
  async function handler(args) {
16
15
  const { packages, exit } = args;
17
16
  try {
18
- const projectConfig = await getProjectConfig();
19
- if (!projectConfig || !projectConfig.projectDir) {
20
- uiLogger.error(commands.project.installDeps.noProjectConfig);
17
+ let projectDir;
18
+ try {
19
+ ({ projectDir } = getProjectConfig());
20
+ }
21
+ catch (error) {
22
+ logError(error);
21
23
  return exit(EXIT_CODES.ERROR);
22
24
  }
23
- const { projectDir } = projectConfig;
24
25
  let installLocations = await getProjectPackageJsonLocations();
25
26
  if (packages) {
26
27
  const { selectedInstallLocations } = await promptUser([
@@ -17,16 +17,23 @@ const describe = commands.project.lint.help.describe;
17
17
  async function handler(args) {
18
18
  const { installMissingDeps, exit } = args;
19
19
  try {
20
- const projectConfig = await getProjectConfig();
21
- if (!projectConfig || !projectConfig.projectDir) {
22
- uiLogger.error(commands.project.lint.noProjectConfig);
20
+ let projectConfig;
21
+ let projectDir;
22
+ try {
23
+ ({ projectConfig, projectDir } = getProjectConfig());
24
+ }
25
+ catch (error) {
26
+ logError(error);
23
27
  return exit(EXIT_CODES.ERROR);
24
28
  }
25
29
  SpinniesManager.init({ succeedColor: 'white' });
26
30
  SpinniesManager.add('lintCheck', {
27
31
  text: commands.project.lint.loading.checking,
28
32
  });
29
- const lintLocations = await getUieLintablePackageJsonLocations(projectConfig);
33
+ const lintLocations = await getUieLintablePackageJsonLocations({
34
+ projectConfig,
35
+ projectDir,
36
+ });
30
37
  const locationsReadyToLint = [];
31
38
  const locationsNeedingPackages = new Map();
32
39
  let skippedDirectoryCount = 0;
@@ -44,7 +51,7 @@ async function handler(args) {
44
51
  SpinniesManager.succeed('lintCheck');
45
52
  if (locationsNeedingPackages.size > 0) {
46
53
  const locationsArray = Array.from(locationsNeedingPackages.keys());
47
- const relativeLocations = locationsArray.map(loc => path.relative(projectConfig.projectDir, loc));
54
+ const relativeLocations = locationsArray.map(loc => path.relative(projectDir, loc));
48
55
  const allMissingPackages = [];
49
56
  for (const packages of locationsNeedingPackages.values()) {
50
57
  allMissingPackages.push(...packages);
@@ -94,7 +101,7 @@ async function handler(args) {
94
101
  const hasModernConfig = hasEslintConfig(location);
95
102
  const hasDeprecatedConfig = hasDeprecatedEslintConfig(location);
96
103
  if (hasDeprecatedConfig) {
97
- const relativePath = path.relative(projectConfig.projectDir, location);
104
+ const relativePath = path.relative(projectDir, location);
98
105
  const deprecatedFiles = getDeprecatedEslintConfigFiles(location);
99
106
  deprecatedConfigDetails.push({
100
107
  path: relativePath,
@@ -110,7 +117,7 @@ async function handler(args) {
110
117
  uiLogger.warn(commands.project.lint.deprecatedEslintConfigWarning(deprecatedConfigDetails));
111
118
  }
112
119
  if (locationsNeedingConfig.length > 0) {
113
- const relativeLocations = locationsNeedingConfig.map(loc => path.relative(projectConfig.projectDir, loc));
120
+ const relativeLocations = locationsNeedingConfig.map(loc => path.relative(projectDir, loc));
114
121
  const { shouldCreateConfig } = await promptUser([
115
122
  {
116
123
  name: 'shouldCreateConfig',
@@ -123,7 +130,7 @@ async function handler(args) {
123
130
  SpinniesManager.add('lintConfigCreate', {
124
131
  text: commands.project.lint.loading.creatingConfig,
125
132
  });
126
- const platformVersion = projectConfig.projectConfig?.platformVersion ?? null;
133
+ const platformVersion = projectConfig.platformVersion ?? null;
127
134
  const createdConfigs = [];
128
135
  for (const location of locationsNeedingConfig) {
129
136
  const configPath = await createEslintConfig(location, platformVersion);
@@ -159,7 +166,7 @@ async function handler(args) {
159
166
  SpinniesManager.add('lintRun', {
160
167
  text: commands.project.lint.loading.linting,
161
168
  });
162
- const { success, results } = await lintPackages(locationsReadyToLint, projectConfig.projectDir);
169
+ const { success, results } = await lintPackages(locationsReadyToLint, projectDir);
163
170
  SpinniesManager.succeed('lintRun');
164
171
  displayLintResults(results);
165
172
  if (!success) {
@@ -1,4 +1,5 @@
1
- import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- type ProjectListArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs;
1
+ import { AccountArgs, CommonArgs, ConfigArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ import { ProjectListJsonOutput } from '../../lib/jsonOutput/projectList.js';
3
+ export type ProjectListArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<ProjectListJsonOutput>;
3
4
  declare const projectListCommand: YargsCommandModule<unknown, ProjectListArgs>;
4
5
  export default projectListCommand;
@@ -6,6 +6,7 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
6
6
  import { uiLogger } from '../../lib/ui/logger.js';
7
7
  import { commands } from '../../lang/en.js';
8
8
  import { renderTable } from '../../ui/render.js';
9
+ import { ProjectListSchema, mapProjectToListItem, } from '../../lib/jsonOutput/projectList.js';
9
10
  const command = ['list', 'ls'];
10
11
  const describe = commands.project.list.describe;
11
12
  async function getProjectData(accountId) {
@@ -23,7 +24,7 @@ function formatProjectsAsTableRows(projects) {
23
24
  return projectListData;
24
25
  }
25
26
  async function handler(args) {
26
- const { derivedAccountId, exit } = args;
27
+ const { derivedAccountId, formatOutputAsJson, exit, addJsonOutput } = args;
27
28
  let projectData;
28
29
  try {
29
30
  projectData = await getProjectData(derivedAccountId);
@@ -33,9 +34,20 @@ async function handler(args) {
33
34
  return exit(EXIT_CODES.ERROR);
34
35
  }
35
36
  if (projectData.length === 0) {
37
+ if (formatOutputAsJson) {
38
+ addJsonOutput({ accountId: derivedAccountId, results: [] });
39
+ return exit(EXIT_CODES.SUCCESS);
40
+ }
36
41
  uiLogger.error(commands.project.list.errors.noProjectsFound(derivedAccountId));
37
42
  return exit(EXIT_CODES.ERROR);
38
43
  }
44
+ if (formatOutputAsJson) {
45
+ addJsonOutput({
46
+ accountId: derivedAccountId,
47
+ results: projectData.map(mapProjectToListItem),
48
+ });
49
+ return exit(EXIT_CODES.SUCCESS);
50
+ }
39
51
  const projectListData = formatProjectsAsTableRows(projectData);
40
52
  const tableHeader = [
41
53
  commands.project.list.labels.name,
@@ -45,7 +57,10 @@ async function handler(args) {
45
57
  renderTable(tableHeader, projectListData);
46
58
  }
47
59
  function projectListBuilder(yargs) {
48
- yargs.example([['$0 project list']]);
60
+ yargs.example([
61
+ ['$0 project list', commands.project.list.examples.default],
62
+ ['$0 project list --json', commands.project.list.examples.json],
63
+ ]);
49
64
  return yargs;
50
65
  }
51
66
  const builder = makeYargsBuilder(projectListBuilder, command, describe, {
@@ -53,11 +68,14 @@ const builder = makeYargsBuilder(projectListBuilder, command, describe, {
53
68
  useConfigOptions: true,
54
69
  useAccountOptions: true,
55
70
  useEnvironmentOptions: true,
71
+ useJSONOutputOptions: true,
56
72
  });
57
73
  const projectListCommand = {
58
74
  command,
59
75
  describe,
60
- handler: makeWrappedYargsHandler('project-list', handler),
76
+ handler: makeWrappedYargsHandler('project-list', handler, {
77
+ jsonOutputSchema: ProjectListSchema,
78
+ }),
61
79
  builder,
62
80
  };
63
81
  export default projectListCommand;
@@ -1,5 +1,5 @@
1
1
  import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- import { ProjectBuildsListJsonOutput } from '../../lib/jsonOutput.js';
2
+ import { ProjectBuildsListJsonOutput } from '../../lib/jsonOutput/listBuilds.js';
3
3
  export type ProjectListBuildsArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<ProjectBuildsListJsonOutput> & {
4
4
  project?: string;
5
5
  limit?: number;