@hubspot/cli 8.2.1-experimental.0 → 8.2.1-experimental.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/commands/cms/function/server.js +14 -7
  2. package/commands/cms/theme/preview.js +8 -1
  3. package/commands/getStarted.js +15 -20
  4. package/commands/project/add.js +7 -6
  5. package/commands/project/appInstallStatus.js +7 -3
  6. package/commands/project/create.js +9 -6
  7. package/commands/project/deploy.js +13 -11
  8. package/commands/project/dev/index.js +9 -7
  9. package/commands/project/dev/unifiedFlow.js +8 -0
  10. package/commands/project/download.js +3 -3
  11. package/commands/project/info.js +7 -4
  12. package/commands/project/installApp.js +8 -3
  13. package/commands/project/installDeps.js +6 -5
  14. package/commands/project/lint.js +16 -9
  15. package/commands/project/listBuilds.d.ts +3 -2
  16. package/commands/project/listBuilds.js +40 -13
  17. package/commands/project/migrate.js +14 -12
  18. package/commands/project/open.js +15 -7
  19. package/commands/project/profile/add.js +8 -4
  20. package/commands/project/profile/delete.js +8 -4
  21. package/commands/project/release/create.js +4 -6
  22. package/commands/project/release/info.js +3 -3
  23. package/commands/project/release/list.js +3 -3
  24. package/commands/project/updateDeps.js +6 -5
  25. package/commands/project/upload.js +26 -15
  26. package/commands/project/validate.js +14 -97
  27. package/commands/project/watch.js +8 -11
  28. package/commands/project.js +1 -1
  29. package/lang/en.d.ts +19 -18
  30. package/lang/en.js +23 -20
  31. package/lib/app/migrate.js +5 -7
  32. package/lib/cms/serverlessDevRuntime.d.ts +1 -0
  33. package/lib/cms/serverlessDevRuntime.js +72 -0
  34. package/lib/constants.d.ts +2 -0
  35. package/lib/constants.js +2 -0
  36. package/lib/dependencyManagement.js +3 -10
  37. package/lib/doctor/DiagnosticInfoBuilder.d.ts +2 -2
  38. package/lib/doctor/DiagnosticInfoBuilder.js +8 -8
  39. package/lib/doctor/Doctor.js +7 -9
  40. package/lib/errors/ProjectErrors.d.ts +0 -6
  41. package/lib/errors/ProjectErrors.js +0 -12
  42. package/lib/errors/ProjectValidationError.d.ts +2 -1
  43. package/lib/errors/ProjectValidationError.js +3 -1
  44. package/lib/getStartedV2Actions.js +14 -18
  45. package/lib/jsonOutput.d.ts +86 -0
  46. package/lib/jsonOutput.js +77 -0
  47. package/lib/npm/packageJson.js +1 -1
  48. package/lib/projects/ProjectLogsManager.js +1 -4
  49. package/lib/projects/config.d.ts +6 -5
  50. package/lib/projects/config.js +7 -38
  51. package/lib/projects/localDev/LocalDevLogger.d.ts +4 -0
  52. package/lib/projects/localDev/LocalDevLogger.js +18 -0
  53. package/lib/projects/localDev/LocalDevProcess.d.ts +11 -1
  54. package/lib/projects/localDev/LocalDevProcess.js +91 -9
  55. package/lib/projects/localDev/LocalDevState.d.ts +7 -1
  56. package/lib/projects/localDev/LocalDevState.js +19 -1
  57. package/lib/projects/localDev/LocalDevWatcher.js +1 -1
  58. package/lib/projects/localDev/LocalDevWebsocketServer.d.ts +1 -0
  59. package/lib/projects/localDev/LocalDevWebsocketServer.js +23 -1
  60. package/lib/projects/localDev/localDevWebsocketServerUtils.d.ts +2 -1
  61. package/lib/projects/localDev/localDevWebsocketServerUtils.js +3 -0
  62. package/lib/projects/projectProfiles.d.ts +2 -2
  63. package/lib/projects/projectProfiles.js +0 -3
  64. package/lib/projects/release.js +1 -1
  65. package/lib/projects/uieLinting.js +0 -3
  66. package/lib/projects/validate.d.ts +9 -0
  67. package/lib/projects/validate.js +96 -0
  68. package/lib/theme/migrate.js +1 -4
  69. package/mcp-server/tools/project/GetBuildLogsTool.js +2 -3
  70. package/mcp-server/tools/project/GetBuildStatusTool.js +2 -3
  71. package/mcp-server/tools/project/UploadProjectTools.js +3 -5
  72. package/package.json +11 -10
  73. package/types/LocalDev.d.ts +7 -0
  74. package/types/Projects.d.ts +1 -5
  75. package/types/Yargs.d.ts +1 -0
@@ -1,18 +1,25 @@
1
1
  import { uiLogger } from '../../../lib/ui/logger.js';
2
- // This package is not typed, so we need to use require
3
- import { start as startTestServer } from '@hubspot/serverless-dev-runtime';
4
2
  import { commands } from '../../../lang/en.js';
3
+ import { EXIT_CODES } from '../../../lib/enums/exitCodes.js';
4
+ import { startServerlessDevRuntime } from '../../../lib/cms/serverlessDevRuntime.js';
5
+ import { logError } from '../../../lib/errorHandlers/index.js';
5
6
  import { makeWrappedYargsHandler } from '../../../lib/yargs/makeWrappedYargsHandler.js';
6
7
  import { makeYargsBuilder } from '../../../lib/yargsUtils.js';
7
8
  const command = 'server <path>';
8
9
  const describe = undefined;
9
10
  async function handler(args) {
10
- const { path: functionPath, derivedAccountId } = args;
11
+ const { path: functionPath, derivedAccountId, exit } = args;
11
12
  uiLogger.debug(commands.cms.subcommands.function.subcommands.server.debug.startingServer(functionPath));
12
- startTestServer({
13
- accountId: derivedAccountId,
14
- ...args,
15
- });
13
+ try {
14
+ await startServerlessDevRuntime({
15
+ accountId: derivedAccountId,
16
+ ...args,
17
+ });
18
+ }
19
+ catch (e) {
20
+ logError(e);
21
+ return exit(EXIT_CODES.ERROR);
22
+ }
16
23
  }
17
24
  function functionServerBuilder(yargs) {
18
25
  yargs.positional('path', {
@@ -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);
@@ -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)) {
@@ -51,13 +51,16 @@ async function handler(args) {
51
51
  .join(','),
52
52
  });
53
53
  const projectDest = path.resolve(getCwd(), projectNameAndDestPromptResponse.dest);
54
- const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDest);
55
54
  // 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);
55
+ try {
56
+ const { projectDir: existingProjectDir } = getProjectConfig(projectDest);
57
+ if (projectDest.startsWith(existingProjectDir)) {
58
+ uiLogger.error(commands.project.create.errors.cannotNestProjects(existingProjectDir));
59
+ return exit(EXIT_CODES.ERROR);
60
+ }
61
+ }
62
+ catch {
63
+ // Not in a project directory, which is the expected case for create
61
64
  }
62
65
  const components = generateComponentPaths({
63
66
  selectProjectTemplatePromptResponse,
@@ -3,7 +3,7 @@ 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';
@@ -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);
@@ -110,6 +107,11 @@ function projectDevBuilder(yargs) {
110
107
  description: commands.project.dev.options.port,
111
108
  default: LOCAL_DEV_DEFAULT_PORT,
112
109
  });
110
+ yargs.option('auto-upload', {
111
+ type: 'boolean',
112
+ description: commands.project.dev.options.autoUpload,
113
+ default: false,
114
+ });
113
115
  yargs.example([['$0 project dev', commands.project.dev.examples.default]]);
114
116
  yargs.conflicts('profile', 'testing-account');
115
117
  yargs.conflicts('profile', 'project-account');
@@ -155,6 +155,13 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
155
155
  noLogs: true,
156
156
  });
157
157
  let project = uploadedProject;
158
+ // Fail fast before the slower project checks below.
159
+ const autoDeployEnabled = (project?.deployedBuild ?? project?.latestBuild)?.isAutoDeployEnabled ??
160
+ true;
161
+ if (args.autoUpload && !autoDeployEnabled) {
162
+ uiLogger.error(commands.project.dev.errors.autoUploadRequiresAutoDeploy);
163
+ return exit(EXIT_CODES.ERROR);
164
+ }
158
165
  if (projectExists && project) {
159
166
  await compareLocalProjectToDeployed(projectConfig, targetProjectAccountId, project.deployedBuild?.buildId, projectNodes, exit, args.profile);
160
167
  }
@@ -189,6 +196,7 @@ export async function unifiedProjectDevFlow({ args, targetProjectAccountId, prov
189
196
  projectData: project,
190
197
  env,
191
198
  actions: { exit },
199
+ autoUploadEnabled: Boolean(args.autoUpload) && autoDeployEnabled,
192
200
  });
193
201
  const websocketServer = new LocalDevWebsocketServer(localDevProcess, args.debug);
194
202
  const watcher = new LocalDevWatcher(localDevProcess);
@@ -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,7 +1,7 @@
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';
@@ -14,9 +14,12 @@ 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)) {
@@ -4,6 +4,7 @@ import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler
4
4
  import { InstallAppSchema, } from '../../lib/jsonOutput.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,5 +1,6 @@
1
- import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, YargsCommandModule } from '../../types/Yargs.js';
2
- export type ProjectListBuildsArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & {
1
+ import { CommonArgs, ConfigArgs, AccountArgs, EnvironmentArgs, JSONOutputArgs, YargsCommandModule } from '../../types/Yargs.js';
2
+ import { ProjectBuildsListJsonOutput } from '../../lib/jsonOutput.js';
3
+ export type ProjectListBuildsArgs = CommonArgs & ConfigArgs & AccountArgs & EnvironmentArgs & JSONOutputArgs<ProjectBuildsListJsonOutput> & {
3
4
  project?: string;
4
5
  limit?: number;
5
6
  };
@@ -1,10 +1,11 @@
1
1
  import { isHubSpotHttpError } from '@hubspot/local-dev-lib/errors/index';
2
2
  import { fetchProject, fetchProjectBuilds, } from '@hubspot/local-dev-lib/api/projects';
3
3
  import { uiLink } from '../../lib/ui/index.js';
4
- import { getProjectConfig, validateProjectConfig, } from '../../lib/projects/config.js';
4
+ import { getProjectConfig } from '../../lib/projects/config.js';
5
5
  import { getProjectDetailUrl } from '../../lib/projects/urls.js';
6
6
  import moment from 'moment';
7
7
  import { promptUser } from '../../lib/prompts/promptUtils.js';
8
+ import { isPromptExitError } from '../../lib/errors/PromptExitError.js';
8
9
  import { uiLogger } from '../../lib/ui/logger.js';
9
10
  import { logError, ApiErrorContext } from '../../lib/errorHandlers/index.js';
10
11
  import { makeWrappedYargsHandler } from '../../lib/yargs/makeWrappedYargsHandler.js';
@@ -12,6 +13,7 @@ import { EXIT_CODES } from '../../lib/enums/exitCodes.js';
12
13
  import { makeYargsBuilder } from '../../lib/yargsUtils.js';
13
14
  import { commands } from '../../lang/en.js';
14
15
  import { renderTable } from '../../ui/render.js';
16
+ import { ProjectBuildsListSchema, mapBuildToJsonOutput, } from '../../lib/jsonOutput.js';
15
17
  const command = 'list-builds';
16
18
  const describe = commands.project.listBuilds.describe;
17
19
  async function fetchAndDisplayBuilds(accountId, project, options) {
@@ -40,7 +42,7 @@ async function fetchAndDisplayBuilds(accountId, project, options) {
40
42
  });
41
43
  renderTable(['Build ID', 'Status', 'Completed', 'Duration', 'Details'], builds);
42
44
  }
43
- if (options && options.after) {
45
+ if (options.after) {
44
46
  if (results.length > 0) {
45
47
  uiLogger.log(commands.project.listBuilds.showingNextBuilds(results.length, project.name));
46
48
  }
@@ -48,7 +50,8 @@ async function fetchAndDisplayBuilds(accountId, project, options) {
48
50
  else {
49
51
  uiLogger.log(commands.project.listBuilds.showingRecentBuilds(results.length, project.name, uiLink(commands.project.listBuilds.viewAllBuildsLink, getProjectDetailUrl(project.name, accountId))));
50
52
  }
51
- if (paging && paging.next) {
53
+ const canPromptForMore = options.limit === undefined && Boolean(process.stdin.isTTY);
54
+ if (paging?.next?.after && canPromptForMore) {
52
55
  await promptUser({
53
56
  name: 'more',
54
57
  message: commands.project.listBuilds.continueOrExitPrompt,
@@ -60,27 +63,39 @@ async function fetchAndDisplayBuilds(accountId, project, options) {
60
63
  }
61
64
  }
62
65
  async function handler(args) {
63
- const { project: projectFlagValue, limit, derivedAccountId, exit } = args;
66
+ const { project: projectFlagValue, limit, derivedAccountId, exit, json: formatOutputAsJson, addJsonOutput, } = args;
64
67
  let projectName = projectFlagValue;
65
68
  if (!projectName) {
66
- const { projectConfig, projectDir } = await getProjectConfig();
67
69
  try {
68
- validateProjectConfig(projectConfig, projectDir);
70
+ const { projectConfig } = getProjectConfig();
71
+ projectName = projectConfig.name;
69
72
  }
70
73
  catch (error) {
71
74
  logError(error);
72
75
  return exit(EXIT_CODES.ERROR);
73
76
  }
74
- if (!projectConfig) {
75
- return exit(EXIT_CODES.ERROR);
76
- }
77
- projectName = projectConfig.name;
78
77
  }
79
78
  try {
80
79
  const { data: project } = await fetchProject(derivedAccountId, projectName);
81
- await fetchAndDisplayBuilds(derivedAccountId, project, { limit });
80
+ if (formatOutputAsJson) {
81
+ const { data: { results, paging }, } = await fetchProjectBuilds(derivedAccountId, project.name, { limit });
82
+ addJsonOutput({
83
+ projectName: project.name,
84
+ deployedBuildId: project.deployedBuildId,
85
+ results: results.map(build => mapBuildToJsonOutput(build, project.deployedBuildId)),
86
+ paging: paging?.next?.after
87
+ ? { next: { after: paging.next.after } }
88
+ : undefined,
89
+ });
90
+ }
91
+ else {
92
+ await fetchAndDisplayBuilds(derivedAccountId, project, { limit });
93
+ }
82
94
  }
83
95
  catch (e) {
96
+ if (isPromptExitError(e)) {
97
+ return exit(e.exitCode);
98
+ }
84
99
  if (isHubSpotHttpError(e) && e.status === 404) {
85
100
  uiLogger.error(commands.project.listBuilds.errors.projectNotFound(projectName));
86
101
  }
@@ -90,6 +105,7 @@ async function handler(args) {
90
105
  projectName,
91
106
  }));
92
107
  }
108
+ return exit(EXIT_CODES.ERROR);
93
109
  }
94
110
  return exit(EXIT_CODES.SUCCESS);
95
111
  }
@@ -101,11 +117,19 @@ function projectListBuildsBuilder(yargs) {
101
117
  },
102
118
  limit: {
103
119
  describe: commands.project.listBuilds.options.limit.describe,
104
- type: 'string',
120
+ type: 'number',
105
121
  },
106
122
  });
107
123
  yargs.example([
108
124
  ['$0 project list-builds', commands.project.listBuilds.examples.default],
125
+ [
126
+ '$0 project list-builds --limit=5',
127
+ commands.project.listBuilds.examples.withLimit,
128
+ ],
129
+ [
130
+ '$0 project list-builds --json',
131
+ commands.project.listBuilds.examples.json,
132
+ ],
109
133
  ]);
110
134
  return yargs;
111
135
  }
@@ -114,11 +138,14 @@ const builder = makeYargsBuilder(projectListBuildsBuilder, command, describe, {
114
138
  useConfigOptions: true,
115
139
  useAccountOptions: true,
116
140
  useEnvironmentOptions: true,
141
+ useJSONOutputOptions: true,
117
142
  });
118
143
  const projectListBuildsCommand = {
119
144
  command,
120
145
  describe,
121
- handler: makeWrappedYargsHandler('project-list-builds', handler),
146
+ handler: makeWrappedYargsHandler('project-list-builds', handler, {
147
+ jsonOutputSchema: ProjectBuildsListSchema,
148
+ }),
122
149
  builder,
123
150
  };
124
151
  export default projectListBuildsCommand;