@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
@@ -4,18 +4,6 @@ export class ProjectNestingError extends Error {
4
4
  this.name = 'ProjectNestingError';
5
5
  }
6
6
  }
7
- export class ProjectConfigNotFoundError extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = 'ProjectConfigNotFoundError';
11
- }
12
- }
13
- export class ProjectValidationError extends Error {
14
- constructor(message) {
15
- super(message);
16
- this.name = 'ProjectValidationError';
17
- }
18
- }
19
7
  export class ProjectUploadError extends Error {
20
8
  constructor(message, cause) {
21
9
  super(message, { cause });
@@ -1,4 +1,5 @@
1
+ import { ProjectConfigValidationError } from '@hubspot/project-parsing-lib/projects';
1
2
  export default class ProjectValidationError extends Error {
2
3
  constructor(message: string, options?: ErrorOptions);
3
4
  }
4
- export declare function isProjectValidationError(err: unknown): err is ProjectValidationError;
5
+ export declare function isProjectValidationError(err: unknown): err is ProjectValidationError | ProjectConfigValidationError;
@@ -1,3 +1,4 @@
1
+ import { ProjectConfigValidationError } from '@hubspot/project-parsing-lib/projects';
1
2
  export default class ProjectValidationError extends Error {
2
3
  constructor(message, options) {
3
4
  super(message, options);
@@ -5,5 +6,6 @@ export default class ProjectValidationError extends Error {
5
6
  }
6
7
  }
7
8
  export function isProjectValidationError(err) {
8
- return err instanceof ProjectValidationError;
9
+ return (err instanceof ProjectValidationError ||
10
+ err instanceof ProjectConfigValidationError);
9
11
  }
@@ -11,8 +11,8 @@ import { translate, } from '@hubspot/project-parsing-lib/translate';
11
11
  import { commands, lib } from '../lang/en.js';
12
12
  import { getStaticAuthAppInstallUrl } from '../lib/app/urls.js';
13
13
  import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, PROJECT_CONFIG_FILE, } from '../lib/constants.js';
14
- import { ProjectNestingError, ProjectConfigNotFoundError, ProjectValidationError, ProjectUploadError, ProjectBuildDeployError, } from './errors/ProjectErrors.js';
15
- import { getProjectConfig, validateProjectConfig, writeProjectConfig, } from '../lib/projects/config.js';
14
+ import { ProjectNestingError, ProjectUploadError, ProjectBuildDeployError, } from './errors/ProjectErrors.js';
15
+ import { getProjectConfig, writeProjectConfig, } from '../lib/projects/config.js';
16
16
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
17
17
  import { pollProjectBuildAndDeploy } from '../lib/projects/pollProjectBuildAndDeploy.js';
18
18
  import { handleProjectUpload } from '../lib/projects/upload.js';
@@ -29,11 +29,17 @@ export async function createProjectAction({ projectName, projectDest, }) {
29
29
  : 'Invalid project directory');
30
30
  }
31
31
  const projectDestAbsolute = path.resolve(getCwd(), projectDest);
32
- const { projectConfig: existingProjectConfig, projectDir: existingProjectDir, } = await getProjectConfig(projectDestAbsolute);
33
- if (existingProjectConfig &&
34
- existingProjectDir &&
35
- projectDestAbsolute.startsWith(existingProjectDir)) {
36
- throw new ProjectNestingError(commands.project.create.errors.cannotNestProjects(existingProjectDir));
32
+ try {
33
+ const { projectDir: existingProjectDir } = getProjectConfig(projectDestAbsolute);
34
+ if (projectDestAbsolute.startsWith(existingProjectDir)) {
35
+ throw new ProjectNestingError(commands.project.create.errors.cannotNestProjects(existingProjectDir));
36
+ }
37
+ }
38
+ catch (error) {
39
+ if (error instanceof ProjectNestingError) {
40
+ throw error;
41
+ }
42
+ // Not in a project directory, which is the expected case
37
43
  }
38
44
  try {
39
45
  await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectDestAbsolute, {
@@ -88,17 +94,7 @@ async function buildProjectMetadata(projectDir, projectConfig, accountId) {
88
94
  };
89
95
  }
90
96
  export async function uploadAndDeployAction({ accountId, projectDest, }) {
91
- const { projectConfig, projectDir } = await getProjectConfig(projectDest);
92
- if (!projectConfig || !projectDir) {
93
- throw new ProjectConfigNotFoundError(commands.getStarted.errors.configFileNotFound);
94
- }
95
- try {
96
- validateProjectConfig(projectConfig, projectDir);
97
- }
98
- catch (error) {
99
- const message = error instanceof Error ? error.message : String(error);
100
- throw new ProjectValidationError(message);
101
- }
97
+ const { projectConfig, projectDir } = getProjectConfig(projectDest);
102
98
  const env = getConfigAccountEnvironment(accountId);
103
99
  try {
104
100
  const { result, uploadError } = await handleProjectUpload({
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
3
+ import { Build } from '@hubspot/local-dev-lib/types/Build';
2
4
  import { Release } from '../api/releases.js';
3
5
  declare const ReleaseComponentSchema: z.ZodObject<{
4
6
  buildType: z.ZodString;
@@ -35,6 +37,74 @@ export declare const ReleaseListSchema: z.ZodObject<{
35
37
  }, z.core.$strip>;
36
38
  }, z.core.$strip>>;
37
39
  }, z.core.$strip>;
40
+ declare const BuildSubbuildStatusSchema: z.ZodObject<{
41
+ buildName: z.ZodString;
42
+ buildType: z.ZodString;
43
+ status: z.ZodString;
44
+ errorMessage: z.ZodOptional<z.ZodString>;
45
+ startedAt: z.ZodOptional<z.ZodString>;
46
+ finishedAt: z.ZodOptional<z.ZodString>;
47
+ rootPath: z.ZodOptional<z.ZodString>;
48
+ id: z.ZodOptional<z.ZodString>;
49
+ visible: z.ZodOptional<z.ZodBoolean>;
50
+ }, z.core.$strip>;
51
+ export declare const BuildSchema: z.ZodObject<{
52
+ buildId: z.ZodNumber;
53
+ status: z.ZodString;
54
+ isDeployed: z.ZodBoolean;
55
+ isAutoDeployEnabled: z.ZodOptional<z.ZodBoolean>;
56
+ deployableState: z.ZodOptional<z.ZodString>;
57
+ platformVersion: z.ZodOptional<z.ZodString>;
58
+ uploadMessage: z.ZodOptional<z.ZodString>;
59
+ enqueuedAt: z.ZodOptional<z.ZodString>;
60
+ startedAt: z.ZodOptional<z.ZodString>;
61
+ finishedAt: z.ZodOptional<z.ZodString>;
62
+ createdAt: z.ZodOptional<z.ZodString>;
63
+ subbuildStatuses: z.ZodArray<z.ZodObject<{
64
+ buildName: z.ZodString;
65
+ buildType: z.ZodString;
66
+ status: z.ZodString;
67
+ errorMessage: z.ZodOptional<z.ZodString>;
68
+ startedAt: z.ZodOptional<z.ZodString>;
69
+ finishedAt: z.ZodOptional<z.ZodString>;
70
+ rootPath: z.ZodOptional<z.ZodString>;
71
+ id: z.ZodOptional<z.ZodString>;
72
+ visible: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$strip>>;
74
+ }, z.core.$strip>;
75
+ export declare const ProjectBuildsListSchema: z.ZodObject<{
76
+ projectName: z.ZodString;
77
+ deployedBuildId: z.ZodOptional<z.ZodNumber>;
78
+ results: z.ZodArray<z.ZodObject<{
79
+ buildId: z.ZodNumber;
80
+ status: z.ZodString;
81
+ isDeployed: z.ZodBoolean;
82
+ isAutoDeployEnabled: z.ZodOptional<z.ZodBoolean>;
83
+ deployableState: z.ZodOptional<z.ZodString>;
84
+ platformVersion: z.ZodOptional<z.ZodString>;
85
+ uploadMessage: z.ZodOptional<z.ZodString>;
86
+ enqueuedAt: z.ZodOptional<z.ZodString>;
87
+ startedAt: z.ZodOptional<z.ZodString>;
88
+ finishedAt: z.ZodOptional<z.ZodString>;
89
+ createdAt: z.ZodOptional<z.ZodString>;
90
+ subbuildStatuses: z.ZodArray<z.ZodObject<{
91
+ buildName: z.ZodString;
92
+ buildType: z.ZodString;
93
+ status: z.ZodString;
94
+ errorMessage: z.ZodOptional<z.ZodString>;
95
+ startedAt: z.ZodOptional<z.ZodString>;
96
+ finishedAt: z.ZodOptional<z.ZodString>;
97
+ rootPath: z.ZodOptional<z.ZodString>;
98
+ id: z.ZodOptional<z.ZodString>;
99
+ visible: z.ZodOptional<z.ZodBoolean>;
100
+ }, z.core.$strip>>;
101
+ }, z.core.$strip>>;
102
+ paging: z.ZodOptional<z.ZodObject<{
103
+ next: z.ZodObject<{
104
+ after: z.ZodString;
105
+ }, z.core.$strip>;
106
+ }, z.core.$strip>>;
107
+ }, z.core.$strip>;
38
108
  export declare const ProjectInfoSchema: z.ZodObject<{
39
109
  projectName: z.ZodString;
40
110
  platformVersion: z.ZodString;
@@ -58,7 +128,17 @@ declare const PreviewSchema: z.ZodObject<{
58
128
  releaseTag: z.ZodOptional<z.ZodString>;
59
129
  succeeded: z.ZodBoolean;
60
130
  }, z.core.$strip>;
131
+ declare const TargetAccountSchema: z.ZodObject<{
132
+ accountId: z.ZodNumber;
133
+ accountName: z.ZodOptional<z.ZodString>;
134
+ accountType: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>;
61
136
  export declare const UploadSchema: z.ZodObject<{
137
+ targetAccount: z.ZodOptional<z.ZodObject<{
138
+ accountId: z.ZodNumber;
139
+ accountName: z.ZodOptional<z.ZodString>;
140
+ accountType: z.ZodOptional<z.ZodString>;
141
+ }, z.core.$strip>>;
62
142
  buildId: z.ZodOptional<z.ZodNumber>;
63
143
  deployId: z.ZodOptional<z.ZodNumber>;
64
144
  preview: z.ZodOptional<z.ZodObject<{
@@ -98,12 +178,18 @@ export declare const CreateTestAccountSchema: z.ZodObject<{
98
178
  export type ReleaseComponentJsonOutput = z.infer<typeof ReleaseComponentSchema>;
99
179
  export type ReleaseJsonOutput = z.infer<typeof ReleaseSchema>;
100
180
  export type ReleaseListJsonOutput = z.infer<typeof ReleaseListSchema>;
181
+ export type BuildSubbuildStatusJsonOutput = z.infer<typeof BuildSubbuildStatusSchema>;
182
+ export type BuildJsonOutput = z.infer<typeof BuildSchema>;
183
+ export type ProjectBuildsListJsonOutput = z.infer<typeof ProjectBuildsListSchema>;
101
184
  export type ProjectInfoJsonOutput = z.infer<typeof ProjectInfoSchema>;
102
185
  export type PreviewJsonOutput = z.infer<typeof PreviewSchema>;
186
+ export type TargetAccountJsonOutput = z.infer<typeof TargetAccountSchema>;
103
187
  export type UploadJsonOutput = z.infer<typeof UploadSchema>;
104
188
  export type DeployJsonOutput = z.infer<typeof DeploySchema>;
105
189
  export type InstallStatusJsonOutput = z.infer<typeof InstallStatusSchema>;
106
190
  export type InstallAppJsonOutput = z.infer<typeof InstallAppSchema>;
107
191
  export type CreateTestAccountJsonOutput = z.infer<typeof CreateTestAccountSchema>;
192
+ export declare function mapAccountToTargetAccount(accountId: number, account?: HubSpotConfigAccount | null): TargetAccountJsonOutput;
193
+ export declare function mapBuildToJsonOutput(build: Build, deployedBuildId?: number): BuildJsonOutput;
108
194
  export declare function mapReleaseToJsonOutput(release: Release): ReleaseJsonOutput;
109
195
  export {};
package/lib/jsonOutput.js CHANGED
@@ -21,6 +21,43 @@ export const ReleaseListSchema = z.object({
21
21
  })
22
22
  .optional(),
23
23
  });
24
+ const BuildSubbuildStatusSchema = z.object({
25
+ buildName: z.string(),
26
+ buildType: z.string(),
27
+ status: z.string(),
28
+ errorMessage: z.string().optional(),
29
+ startedAt: z.string().optional(),
30
+ finishedAt: z.string().optional(),
31
+ rootPath: z.string().optional(),
32
+ id: z.string().optional(),
33
+ visible: z.boolean().optional(),
34
+ });
35
+ export const BuildSchema = z.object({
36
+ buildId: z.number(),
37
+ status: z.string(),
38
+ isDeployed: z.boolean(),
39
+ isAutoDeployEnabled: z.boolean().optional(),
40
+ deployableState: z.string().optional(),
41
+ platformVersion: z.string().optional(),
42
+ uploadMessage: z.string().optional(),
43
+ enqueuedAt: z.string().optional(),
44
+ startedAt: z.string().optional(),
45
+ finishedAt: z.string().optional(),
46
+ createdAt: z.string().optional(),
47
+ subbuildStatuses: z.array(BuildSubbuildStatusSchema),
48
+ });
49
+ export const ProjectBuildsListSchema = z.object({
50
+ projectName: z.string(),
51
+ deployedBuildId: z.number().optional(),
52
+ results: z.array(BuildSchema),
53
+ paging: z
54
+ .object({
55
+ next: z.object({
56
+ after: z.string(),
57
+ }),
58
+ })
59
+ .optional(),
60
+ });
24
61
  export const ProjectInfoSchema = z.object({
25
62
  projectName: z.string(),
26
63
  platformVersion: z.string(),
@@ -43,7 +80,13 @@ const PreviewSchema = z.object({
43
80
  releaseTag: z.string().optional(),
44
81
  succeeded: z.boolean(),
45
82
  });
83
+ const TargetAccountSchema = z.object({
84
+ accountId: z.number(),
85
+ accountName: z.string().optional(),
86
+ accountType: z.string().optional(),
87
+ });
46
88
  export const UploadSchema = z.object({
89
+ targetAccount: TargetAccountSchema.optional(),
47
90
  buildId: z.number().optional(),
48
91
  deployId: z.number().optional(),
49
92
  preview: PreviewSchema.optional(),
@@ -78,6 +121,40 @@ export const CreateTestAccountSchema = z.object({
78
121
  accountId: z.number().optional(),
79
122
  personalAccessKey: z.string().optional(),
80
123
  });
124
+ export function mapAccountToTargetAccount(accountId, account) {
125
+ return {
126
+ accountId,
127
+ accountName: account?.name,
128
+ accountType: account?.accountType,
129
+ };
130
+ }
131
+ export function mapBuildToJsonOutput(build, deployedBuildId) {
132
+ const { buildId, status, isAutoDeployEnabled, deployableState, platformVersion, uploadMessage, enqueuedAt, startedAt, finishedAt, createdAt, subbuildStatuses, } = build;
133
+ return {
134
+ buildId,
135
+ status,
136
+ isDeployed: buildId === deployedBuildId,
137
+ isAutoDeployEnabled,
138
+ deployableState,
139
+ platformVersion,
140
+ uploadMessage,
141
+ enqueuedAt,
142
+ startedAt,
143
+ finishedAt,
144
+ createdAt,
145
+ subbuildStatuses: subbuildStatuses.map(subbuild => ({
146
+ buildName: subbuild.buildName,
147
+ buildType: subbuild.buildType,
148
+ status: subbuild.status,
149
+ errorMessage: subbuild.errorMessage,
150
+ startedAt: subbuild.startedAt,
151
+ finishedAt: subbuild.finishedAt,
152
+ rootPath: subbuild.rootPath,
153
+ id: subbuild.id,
154
+ visible: subbuild.visible,
155
+ })),
156
+ };
157
+ }
81
158
  export function mapReleaseToJsonOutput(release) {
82
159
  const { releaseTag, buildId, createdAt, components } = release;
83
160
  return {
@@ -92,7 +92,7 @@ const findAllPackageJsonFilesInDirectoryCached = async (directory) => {
92
92
  * @returns Array of absolute paths to package.json files in the project
93
93
  */
94
94
  export async function findAllPackageJsonFilesInProjectCached() {
95
- const projectConfig = await getProjectConfig();
95
+ const projectConfig = getProjectConfig();
96
96
  const { projectDir } = projectConfig;
97
97
  if (!projectDir) {
98
98
  return [];
@@ -34,10 +34,7 @@ class _ProjectLogsManager {
34
34
  this.functions = [];
35
35
  }
36
36
  async init(accountId) {
37
- const { projectConfig } = await getProjectConfig();
38
- if (!projectConfig || !projectConfig.name) {
39
- throw new Error(commands.project.logs.errors.noProjectConfig);
40
- }
37
+ const { projectConfig } = getProjectConfig();
41
38
  this.projectConfig = projectConfig;
42
39
  this.projectName = projectConfig.name;
43
40
  this.accountId = accountId;
@@ -1,9 +1,10 @@
1
- import { ProjectConfig } from '../../types/Projects.js';
1
+ import type { ProjectConfig } from '@hubspot/project-parsing-lib/projects';
2
+ export { ProjectConfigValidationError } from '@hubspot/project-parsing-lib/projects';
3
+ export type { ProjectConfig } from '@hubspot/project-parsing-lib/projects';
2
4
  export declare function writeProjectConfig(configPath: string, config: ProjectConfig): boolean;
3
5
  export declare function getIsInProject(dir?: string): boolean;
4
6
  export interface LoadedProjectConfig {
5
- projectDir: string | null;
6
- projectConfig: ProjectConfig | null;
7
+ projectDir: string;
8
+ projectConfig: ProjectConfig;
7
9
  }
8
- export declare function getProjectConfig(dir?: string): Promise<LoadedProjectConfig>;
9
- export declare function validateProjectConfig(projectConfig: ProjectConfig | null, projectDir: string | null): asserts projectConfig is ProjectConfig;
10
+ export declare function getProjectConfig(dir?: string): LoadedProjectConfig;
@@ -2,10 +2,11 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import findup from 'findup-sync';
4
4
  import { getAbsoluteFilePath, getCwd } from '@hubspot/local-dev-lib/path';
5
+ import { parseProjectConfig, ProjectConfigValidationError, } from '@hubspot/project-parsing-lib/projects';
5
6
  import { PROJECT_CONFIG_FILE } from '../constants.js';
6
7
  import { lib } from '../../lang/en.js';
7
8
  import { uiLogger } from '../ui/logger.js';
8
- import ProjectValidationError from '../errors/ProjectValidationError.js';
9
+ export { ProjectConfigValidationError } from '@hubspot/project-parsing-lib/projects';
9
10
  export function writeProjectConfig(configPath, config) {
10
11
  try {
11
12
  fs.ensureFileSync(configPath);
@@ -30,44 +31,12 @@ function getProjectConfigPath(dir) {
30
31
  });
31
32
  return configPath;
32
33
  }
33
- export async function getProjectConfig(dir) {
34
+ export function getProjectConfig(dir) {
34
35
  const configPath = getProjectConfigPath(dir);
35
36
  if (!configPath) {
36
- return { projectConfig: null, projectDir: null };
37
- }
38
- try {
39
- const config = fs.readFileSync(configPath);
40
- const projectConfig = JSON.parse(config.toString());
41
- return {
42
- projectDir: path.dirname(configPath),
43
- projectConfig,
44
- };
45
- }
46
- catch (e) {
47
- uiLogger.error(lib.projects.getProjectConfig.error);
48
- return { projectConfig: null, projectDir: null };
49
- }
50
- }
51
- export function validateProjectConfig(projectConfig, projectDir) {
52
- if (!projectConfig || !projectDir) {
53
- throw new ProjectValidationError(lib.projects.validateProjectConfig.configNotFound);
54
- }
55
- const missingFields = [];
56
- if (!projectConfig.name) {
57
- missingFields.push('name');
58
- }
59
- if (!projectConfig.srcDir) {
60
- missingFields.push('srcDir');
61
- }
62
- if (missingFields.length > 0) {
63
- throw new ProjectValidationError(lib.projects.validateProjectConfig.configMissingFields(missingFields));
64
- }
65
- const resolvedPath = path.resolve(projectDir, projectConfig.srcDir);
66
- if (!resolvedPath.startsWith(projectDir)) {
67
- const projectConfigFile = path.relative('.', path.join(projectDir, PROJECT_CONFIG_FILE));
68
- throw new ProjectValidationError(lib.projects.validateProjectConfig.srcOutsideProjectDir(projectConfigFile, projectConfig.srcDir));
69
- }
70
- if (!fs.existsSync(resolvedPath)) {
71
- throw new ProjectValidationError(lib.projects.validateProjectConfig.srcDirNotFound(projectConfig.srcDir, projectDir));
37
+ throw new ProjectConfigValidationError(lib.projects.validateProjectConfig.configNotFound);
72
38
  }
39
+ const projectDir = path.dirname(configPath);
40
+ const projectConfig = parseProjectConfig(projectDir);
41
+ return { projectDir, projectConfig };
73
42
  }
@@ -22,6 +22,10 @@ declare class LocalDevLogger {
22
22
  cleanupError(): void;
23
23
  cleanupSuccess(): void;
24
24
  uploadInitiated(): void;
25
+ autoUploadScheduled(filePath: string): void;
26
+ autoUploadTriggered(): void;
27
+ autoUploadInProgress(): void;
28
+ autoUploadToggled(enabled: boolean): void;
25
29
  deployInitiated(): void;
26
30
  projectConfigMismatch(): void;
27
31
  uploadError(error: unknown): void;
@@ -99,6 +99,10 @@ class LocalDevLogger {
99
99
  uiLogger.log('');
100
100
  uiLogger.log(lib.LocalDevManager.localDevUIAutoMessage(this.state.targetTestingAccountId, showWelcomeScreen));
101
101
  }
102
+ if (this.state.autoUploadEnabled) {
103
+ uiLogger.log('');
104
+ uiLogger.log(lib.LocalDevManager.autoUploadEnabled);
105
+ }
102
106
  uiLogger.log('');
103
107
  uiLogger.log(lib.LocalDevManager.quitHelper);
104
108
  uiLine();
@@ -122,6 +126,20 @@ class LocalDevLogger {
122
126
  uploadInitiated() {
123
127
  uiLogger.log(lib.LocalDevProcess.uploadInitiated);
124
128
  }
129
+ autoUploadScheduled(filePath) {
130
+ uiLogger.debug(lib.LocalDevProcess.autoUploadScheduled(filePath));
131
+ }
132
+ autoUploadTriggered() {
133
+ uiLogger.debug(lib.LocalDevProcess.autoUploadTriggered);
134
+ }
135
+ autoUploadInProgress() {
136
+ uiLogger.debug(lib.LocalDevProcess.autoUploadInProgress);
137
+ }
138
+ autoUploadToggled(enabled) {
139
+ uiLogger.log('');
140
+ uiLogger.log(lib.LocalDevProcess.autoUploadToggled(enabled));
141
+ uiLogger.log('');
142
+ }
125
143
  deployInitiated() {
126
144
  uiLogger.log(lib.LocalDevProcess.deployInitiated);
127
145
  }
@@ -8,6 +8,9 @@ declare class LocalDevProcess {
8
8
  private _logger;
9
9
  private devServerManager;
10
10
  private devSessionManager;
11
+ private autoUploadTimeout;
12
+ private pendingAutoUpload;
13
+ private autoUploadStopped;
11
14
  constructor(options: LocalDevStateConstructorOptions);
12
15
  get projectDir(): string;
13
16
  get projectData(): Project;
@@ -17,6 +20,10 @@ declare class LocalDevProcess {
17
20
  [key: string]: IntermediateRepresentationNodeLocalDev;
18
21
  };
19
22
  get logger(): LocalDevLogger;
23
+ get autoUploadEnabled(): boolean;
24
+ setAutoUploadEnabled(enabled: boolean): void;
25
+ get autoUploadAvailable(): boolean;
26
+ private isAutoDeployEnabled;
20
27
  private setupDevServers;
21
28
  private startDevServers;
22
29
  private cleanupDevServers;
@@ -27,10 +34,13 @@ declare class LocalDevProcess {
27
34
  private openLocalDevUi;
28
35
  private updateProjectData;
29
36
  handleFileChange(filePath: string, event: string): Promise<void>;
30
- handleConfigFileChange(): Promise<void>;
37
+ handleConfigFileChange(configFilePath: string): Promise<void>;
38
+ private scheduleAutoUpload;
39
+ private runAutoUpload;
31
40
  start(): Promise<void>;
32
41
  stop(showProgress?: boolean): Promise<void>;
33
42
  uploadProject(): Promise<LocalDevProjectUploadResult>;
43
+ private performUpload;
34
44
  deployLatestBuild(force?: boolean): Promise<LocalDevProjectDeployResult>;
35
45
  addStateListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;
36
46
  sendDevServerMessage(message: LocalDevServerMessage): void;
@@ -17,11 +17,15 @@ import { getLocalDevUiUrl } from '../urls.js';
17
17
  import { CONFIG_LOCAL_STATE_FLAGS, PROJECT_DEPLOY_STATES, } from '../../constants.js';
18
18
  import { lib } from '../../../lang/en.js';
19
19
  import { debugError } from '../../errorHandlers/index.js';
20
+ const AUTO_UPLOAD_DEBOUNCE_MS = 2000;
20
21
  class LocalDevProcess {
21
22
  state;
22
23
  _logger;
23
24
  devServerManager;
24
25
  devSessionManager;
26
+ autoUploadTimeout = null;
27
+ pendingAutoUpload = false;
28
+ autoUploadStopped = false;
25
29
  constructor(options) {
26
30
  this.state = new LocalDevState(options);
27
31
  this._logger = new LocalDevLogger(this.state);
@@ -53,6 +57,27 @@ class LocalDevProcess {
53
57
  get logger() {
54
58
  return this._logger;
55
59
  }
60
+ get autoUploadEnabled() {
61
+ return this.state.autoUploadEnabled;
62
+ }
63
+ setAutoUploadEnabled(enabled) {
64
+ this.state.autoUploadEnabled = enabled;
65
+ this.logger.autoUploadToggled(enabled);
66
+ if (!enabled) {
67
+ if (this.autoUploadTimeout) {
68
+ clearTimeout(this.autoUploadTimeout);
69
+ this.autoUploadTimeout = null;
70
+ }
71
+ this.pendingAutoUpload = false;
72
+ }
73
+ }
74
+ get autoUploadAvailable() {
75
+ return this.isAutoDeployEnabled();
76
+ }
77
+ isAutoDeployEnabled() {
78
+ const { deployedBuild, latestBuild } = this.state.projectData;
79
+ return (deployedBuild ?? latestBuild)?.isAutoDeployEnabled ?? true;
80
+ }
56
81
  async setupDevServers() {
57
82
  try {
58
83
  await this.devServerManager.setup();
@@ -83,17 +108,17 @@ class LocalDevProcess {
83
108
  }
84
109
  }
85
110
  async projectConfigValidForUpload() {
86
- const { projectConfig } = await getProjectConfig();
87
- if (!projectConfig) {
111
+ let projectConfig;
112
+ try {
113
+ ({ projectConfig } = getProjectConfig());
114
+ }
115
+ catch {
88
116
  return false;
89
117
  }
90
- Object.keys(projectConfig).forEach(key => {
118
+ return Object.keys(projectConfig).every(key => {
91
119
  const field = key;
92
- if (projectConfig[field] !== this.state.projectConfig[field]) {
93
- return false;
94
- }
120
+ return projectConfig[field] === this.state.projectConfig[field];
95
121
  });
96
- return true;
97
122
  }
98
123
  async getIntermediateRepresentation(projectNodesAtLastDeploy) {
99
124
  const { intermediateRepresentation } = await translateForLocalDev({
@@ -141,10 +166,52 @@ class LocalDevProcess {
141
166
  catch (e) {
142
167
  this.logger.fileChangeError(e);
143
168
  }
169
+ this.scheduleAutoUpload(filePath);
144
170
  }
145
- async handleConfigFileChange() {
171
+ async handleConfigFileChange(configFilePath) {
146
172
  await this.updateProjectNodes();
147
- this.logger.uploadWarning();
173
+ if (this.state.autoUploadEnabled) {
174
+ this.scheduleAutoUpload(configFilePath);
175
+ }
176
+ else {
177
+ this.logger.uploadWarning();
178
+ }
179
+ }
180
+ scheduleAutoUpload(changedPath) {
181
+ if (!this.state.autoUploadEnabled || this.autoUploadStopped) {
182
+ return;
183
+ }
184
+ this.logger.autoUploadScheduled(changedPath ?? 'project files');
185
+ if (this.autoUploadTimeout) {
186
+ clearTimeout(this.autoUploadTimeout);
187
+ }
188
+ this.autoUploadTimeout = setTimeout(() => {
189
+ this.autoUploadTimeout = null;
190
+ void this.runAutoUpload();
191
+ }, AUTO_UPLOAD_DEBOUNCE_MS);
192
+ }
193
+ async runAutoUpload() {
194
+ if (this.autoUploadStopped) {
195
+ return;
196
+ }
197
+ if (this.state.uploadInProgress) {
198
+ this.logger.autoUploadInProgress();
199
+ this.pendingAutoUpload = true;
200
+ return;
201
+ }
202
+ this.logger.autoUploadTriggered();
203
+ try {
204
+ await this.uploadProject();
205
+ }
206
+ catch (e) {
207
+ this.logger.uploadError(e);
208
+ }
209
+ finally {
210
+ if (this.pendingAutoUpload) {
211
+ this.pendingAutoUpload = false;
212
+ this.scheduleAutoUpload();
213
+ }
214
+ }
148
215
  }
149
216
  async start() {
150
217
  this.logger.resetSpinnies();
@@ -165,6 +232,12 @@ class LocalDevProcess {
165
232
  this.logger.monitorConsoleOutput();
166
233
  }
167
234
  async stop(showProgress = true) {
235
+ this.autoUploadStopped = true;
236
+ this.pendingAutoUpload = false;
237
+ if (this.autoUploadTimeout) {
238
+ clearTimeout(this.autoUploadTimeout);
239
+ this.autoUploadTimeout = null;
240
+ }
168
241
  if (showProgress) {
169
242
  this.logger.cleanupStart();
170
243
  }
@@ -182,6 +255,15 @@ class LocalDevProcess {
182
255
  return this.state.actions.exit(EXIT_CODES.SUCCESS);
183
256
  }
184
257
  async uploadProject() {
258
+ this.state.uploadInProgress = true;
259
+ try {
260
+ return await this.performUpload();
261
+ }
262
+ finally {
263
+ this.state.uploadInProgress = false;
264
+ }
265
+ }
266
+ async performUpload() {
185
267
  this.logger.uploadInitiated();
186
268
  const isUploadable = await this.projectConfigValidForUpload();
187
269
  if (!isUploadable) {
@@ -21,8 +21,10 @@ declare class LocalDevState {
21
21
  private _devServerMessage;
22
22
  private _uploadWarnings;
23
23
  private _devServersStarted;
24
+ private _autoUploadEnabled;
25
+ private _uploadInProgress;
24
26
  actions: LocalDevActions;
25
- constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectData, debug, initialProjectNodes, initialProjectProfileData, profile, env, actions, }: LocalDevStateConstructorOptions);
27
+ constructor({ targetProjectAccountId, targetTestingAccountId, projectConfig, projectDir, projectData, debug, initialProjectNodes, initialProjectProfileData, profile, env, actions, autoUploadEnabled, }: LocalDevStateConstructorOptions);
26
28
  private runListeners;
27
29
  get targetProjectAccountId(): number;
28
30
  get targetTestingAccountId(): number;
@@ -56,6 +58,10 @@ declare class LocalDevState {
56
58
  get uploadWarnings(): Set<string>;
57
59
  get devServersStarted(): boolean;
58
60
  set devServersStarted(started: boolean);
61
+ get autoUploadEnabled(): boolean;
62
+ set autoUploadEnabled(autoUploadEnabled: boolean);
63
+ get uploadInProgress(): boolean;
64
+ set uploadInProgress(inProgress: boolean);
59
65
  addUploadWarning(warning: string): void;
60
66
  clearUploadWarnings(): void;
61
67
  addListener<K extends keyof LocalDevState>(key: K, listener: LocalDevStateListener<K>): void;