@hubspot/cli 8.6.0-beta.1 → 8.7.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 (149) hide show
  1. package/api/releases.d.ts +36 -0
  2. package/api/releases.js +41 -0
  3. package/bin/cli.js +3 -3
  4. package/commands/account/auth.js +2 -2
  5. package/commands/account/clean.js +2 -2
  6. package/commands/account/createOverride.js +2 -2
  7. package/commands/account/info.js +2 -2
  8. package/commands/account/link.js +2 -2
  9. package/commands/account/list.js +2 -2
  10. package/commands/account/remove.js +2 -2
  11. package/commands/account/removeOverride.js +2 -2
  12. package/commands/account/rename.js +2 -2
  13. package/commands/account/unlink.js +2 -2
  14. package/commands/account/use.js +2 -2
  15. package/commands/api.js +2 -2
  16. package/commands/app/migrate.js +2 -2
  17. package/commands/app/secret/add.js +2 -2
  18. package/commands/app/secret/delete.js +2 -2
  19. package/commands/app/secret/list.js +2 -2
  20. package/commands/app/secret/update.js +2 -2
  21. package/commands/auth.js +2 -2
  22. package/commands/cms/app/create.js +2 -2
  23. package/commands/cms/convertFields.js +2 -2
  24. package/commands/cms/delete.js +2 -2
  25. package/commands/cms/fetch.js +2 -2
  26. package/commands/cms/function/create.js +2 -2
  27. package/commands/cms/function/deploy.js +2 -2
  28. package/commands/cms/function/list.js +2 -2
  29. package/commands/cms/function/logs.js +2 -2
  30. package/commands/cms/function/server.js +2 -2
  31. package/commands/cms/getReactModule.js +2 -2
  32. package/commands/cms/lighthouseScore.js +2 -2
  33. package/commands/cms/lint.js +2 -2
  34. package/commands/cms/list.js +2 -2
  35. package/commands/cms/module/create.js +8 -2
  36. package/commands/cms/module/marketplace-validate.js +2 -2
  37. package/commands/cms/mv.js +2 -2
  38. package/commands/cms/template/create.js +2 -2
  39. package/commands/cms/theme/create.js +2 -2
  40. package/commands/cms/theme/generate-selectors.js +2 -2
  41. package/commands/cms/theme/marketplace-validate.js +2 -2
  42. package/commands/cms/theme/preview.js +2 -2
  43. package/commands/cms/upload.js +2 -2
  44. package/commands/cms/watch.js +2 -2
  45. package/commands/cms/webpack/create.js +2 -2
  46. package/commands/completion.js +2 -2
  47. package/commands/config/migrate.js +2 -2
  48. package/commands/config/set.js +2 -2
  49. package/commands/customObject/create.js +2 -2
  50. package/commands/customObject/createSchema.js +2 -2
  51. package/commands/customObject/deleteSchema.js +2 -2
  52. package/commands/customObject/fetchAllSchemas.js +2 -2
  53. package/commands/customObject/fetchSchema.js +2 -2
  54. package/commands/customObject/listSchemas.js +2 -2
  55. package/commands/customObject/updateSchema.js +2 -2
  56. package/commands/doctor.js +2 -2
  57. package/commands/feedback.js +2 -2
  58. package/commands/filemanager/fetch.js +2 -2
  59. package/commands/filemanager/upload.js +2 -2
  60. package/commands/getStarted.js +2 -2
  61. package/commands/hubdb/clear.js +2 -2
  62. package/commands/hubdb/create.js +2 -2
  63. package/commands/hubdb/delete.js +2 -2
  64. package/commands/hubdb/fetch.js +2 -2
  65. package/commands/hubdb/list.js +2 -2
  66. package/commands/init.js +2 -2
  67. package/commands/mcp/setup.js +2 -2
  68. package/commands/mcp/start.js +2 -2
  69. package/commands/open.js +2 -2
  70. package/commands/project/add.js +2 -2
  71. package/commands/project/appInstallStatus.d.ts +2 -2
  72. package/commands/project/appInstallStatus.js +3 -2
  73. package/commands/project/create.js +2 -2
  74. package/commands/project/delete.js +2 -2
  75. package/commands/project/deploy.js +2 -2
  76. package/commands/project/dev/index.js +2 -2
  77. package/commands/project/download.js +2 -2
  78. package/commands/project/info.d.ts +2 -2
  79. package/commands/project/info.js +3 -2
  80. package/commands/project/installDeps.js +2 -2
  81. package/commands/project/lint.js +7 -5
  82. package/commands/project/list.d.ts +2 -2
  83. package/commands/project/list.js +3 -2
  84. package/commands/project/listBuilds.js +2 -2
  85. package/commands/project/logs.js +2 -2
  86. package/commands/project/migrate.js +2 -2
  87. package/commands/project/open.js +2 -2
  88. package/commands/project/profile/add.js +2 -2
  89. package/commands/project/profile/delete.js +2 -2
  90. package/commands/project/release/create.d.ts +7 -0
  91. package/commands/project/release/create.js +159 -0
  92. package/commands/project/release/info.d.ts +6 -0
  93. package/commands/project/release/info.js +147 -0
  94. package/commands/project/release/list.d.ts +6 -0
  95. package/commands/project/release/list.js +111 -0
  96. package/commands/project/release.d.ts +3 -0
  97. package/commands/project/release.js +20 -0
  98. package/commands/project/updateDeps.js +2 -2
  99. package/commands/project/upload.d.ts +3 -0
  100. package/commands/project/upload.js +77 -9
  101. package/commands/project/validate.js +2 -2
  102. package/commands/project/watch.js +2 -2
  103. package/commands/project.js +2 -0
  104. package/commands/sandbox/create.js +2 -2
  105. package/commands/sandbox/delete.js +2 -2
  106. package/commands/secret/addSecret.js +2 -2
  107. package/commands/secret/deleteSecret.js +2 -2
  108. package/commands/secret/listSecret.js +2 -2
  109. package/commands/secret/updateSecret.js +2 -2
  110. package/commands/testAccount/create.js +2 -2
  111. package/commands/testAccount/createConfig.js +2 -2
  112. package/commands/testAccount/delete.js +2 -2
  113. package/commands/testAccount/importData.js +2 -2
  114. package/commands/upgrade.js +2 -2
  115. package/lang/en.d.ts +92 -0
  116. package/lang/en.js +92 -0
  117. package/lib/api/usageTracking.d.ts +29 -0
  118. package/lib/api/usageTracking.js +28 -0
  119. package/lib/commonOpts.js +0 -1
  120. package/lib/constants.d.ts +1 -0
  121. package/lib/constants.js +1 -0
  122. package/lib/projects/localDev/helpers/project.js +1 -1
  123. package/lib/projects/npmAuditOnUpload.d.ts +10 -0
  124. package/lib/projects/npmAuditOnUpload.js +73 -0
  125. package/lib/projects/pollProjectBuildAndDeploy.d.ts +5 -1
  126. package/lib/projects/pollProjectBuildAndDeploy.js +3 -2
  127. package/lib/projects/preview.d.ts +7 -0
  128. package/lib/projects/preview.js +48 -0
  129. package/lib/projects/uieLinting.d.ts +4 -0
  130. package/lib/projects/uieLinting.js +36 -1
  131. package/lib/projects/upload.d.ts +3 -1
  132. package/lib/projects/upload.js +26 -6
  133. package/lib/projects/validateLintConfigOnUpload.d.ts +9 -0
  134. package/lib/projects/validateLintConfigOnUpload.js +45 -0
  135. package/lib/projects/workspaces.d.ts +11 -1
  136. package/lib/projects/workspaces.js +27 -12
  137. package/lib/usageTracking.d.ts +7 -17
  138. package/lib/usageTracking.js +43 -29
  139. package/lib/yargs/makeWrappedYargsHandler.d.ts +3 -0
  140. package/lib/yargs/{makeYargsHandlerWithUsageTracking.js → makeWrappedYargsHandler.js} +29 -3
  141. package/mcp-server/tools/cms/HsCreateFunctionTool.js +2 -2
  142. package/mcp-server/tools/cms/HsCreateModuleTool.js +4 -5
  143. package/mcp-server/tools/cms/HsCreateTemplateTool.js +2 -2
  144. package/mcp-server/tools/cms/HsFunctionLogsTool.js +2 -3
  145. package/mcp-server/tools/cms/HsListFunctionsTool.js +2 -3
  146. package/mcp-server/tools/cms/HsListTool.js +2 -2
  147. package/mcp-server/utils/toolUsageTracking.js +10 -6
  148. package/package.json +4 -4
  149. package/lib/yargs/makeYargsHandlerWithUsageTracking.d.ts +0 -3
@@ -0,0 +1,73 @@
1
+ import path from 'path';
2
+ import { runNpmAuditJson } from '@hubspot/ui-extensions-dev-server';
3
+ import { lib } from '../../lang/en.js';
4
+ import { uiLogger } from '../ui/logger.js';
5
+ export function summarizeNpmAuditJson(source) {
6
+ try {
7
+ const data = JSON.parse(source);
8
+ const errorMessage = data.error?.message ?? data.error?.summary;
9
+ if (errorMessage) {
10
+ return errorMessage;
11
+ }
12
+ const v = data.metadata?.vulnerabilities;
13
+ if (!v) {
14
+ return null;
15
+ }
16
+ const total = v.total ?? 0;
17
+ if (total === 0) {
18
+ return null;
19
+ }
20
+ const severityOrder = [
21
+ 'critical',
22
+ 'high',
23
+ 'moderate',
24
+ 'low',
25
+ 'info',
26
+ ];
27
+ const parts = severityOrder
28
+ .filter(severity => (v[severity] ?? 0) > 0)
29
+ .map(severity => `${v[severity]} ${severity}`);
30
+ return `${total} total (${parts.join(', ')})`;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ export async function runNpmAuditsBeforeProjectUpload({ srcDir, projectDir, parsedPackageJsons, isLegacyPlatform, }) {
37
+ const auditRoots = new Set();
38
+ if (isLegacyPlatform) {
39
+ auditRoots.add(srcDir);
40
+ }
41
+ else {
42
+ for (const { dir } of parsedPackageJsons) {
43
+ auditRoots.add(dir);
44
+ }
45
+ if (auditRoots.size === 0) {
46
+ auditRoots.add(srcDir);
47
+ }
48
+ }
49
+ const auditRootArray = [...auditRoots];
50
+ const results = await Promise.all(auditRootArray.map(auditRoot => runNpmAuditJson(auditRoot)));
51
+ for (let i = 0; i < auditRootArray.length; i++) {
52
+ const auditRoot = auditRootArray[i];
53
+ const result = results[i];
54
+ if (result.skipped) {
55
+ continue;
56
+ }
57
+ const relativeRoot = path.relative(projectDir, auditRoot) || '.';
58
+ const summary = summarizeNpmAuditJson(result.source);
59
+ if (result.exitCode === 127) {
60
+ uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditNpmUnavailable(relativeRoot));
61
+ continue;
62
+ }
63
+ if (summary) {
64
+ uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditIssues(relativeRoot, summary));
65
+ continue;
66
+ }
67
+ if (result.exitCode !== 0) {
68
+ uiLogger.warn(lib.projectUpload.handleProjectUpload.npmAuditNonZeroExit(relativeRoot, result.exitCode));
69
+ continue;
70
+ }
71
+ uiLogger.success(lib.projectUpload.handleProjectUpload.npmAuditClean(relativeRoot));
72
+ }
73
+ }
@@ -6,5 +6,9 @@ type PollTaskStatusFunction<T extends ProjectTask> = (accountId: number, taskNam
6
6
  export declare const pollBuildStatus: PollTaskStatusFunction<Build>;
7
7
  export declare const pollDeployStatus: PollTaskStatusFunction<Deploy>;
8
8
  export declare function displayWarnLogs(accountId: number, projectName: string, taskId: number, isDeploy?: boolean): Promise<void>;
9
- export declare function pollProjectBuildAndDeploy(accountId: number, projectConfig: ProjectConfig, tempFile: FileResult, buildId: number, silenceLogs?: boolean): Promise<ProjectPollResult>;
9
+ type PollOptions = {
10
+ silenceLogs?: boolean;
11
+ skipDeploy?: boolean;
12
+ };
13
+ export declare function pollProjectBuildAndDeploy(accountId: number, projectConfig: ProjectConfig, tempFile: FileResult, buildId: number, options?: PollOptions): Promise<ProjectPollResult>;
10
14
  export {};
@@ -291,7 +291,8 @@ export async function displayWarnLogs(accountId, projectName, taskId, isDeploy =
291
291
  });
292
292
  }
293
293
  }
294
- export async function pollProjectBuildAndDeploy(accountId, projectConfig, tempFile, buildId, silenceLogs = false) {
294
+ export async function pollProjectBuildAndDeploy(accountId, projectConfig, tempFile, buildId, options = {}) {
295
+ const { silenceLogs = false, skipDeploy = false } = options;
295
296
  let buildStatus = await pollBuildStatus(accountId, projectConfig.name, buildId, null, silenceLogs);
296
297
  if (!silenceLogs) {
297
298
  uiLine();
@@ -306,7 +307,7 @@ export async function pollProjectBuildAndDeploy(accountId, projectConfig, tempFi
306
307
  result.succeeded = false;
307
308
  return result;
308
309
  }
309
- else if (buildStatus.isAutoDeployEnabled) {
310
+ else if (buildStatus.isAutoDeployEnabled && !skipDeploy) {
310
311
  if (!silenceLogs) {
311
312
  uiLogger.log(lib.projectBuildAndDeploy.pollProjectBuildAndDeploy.buildSucceededAutomaticallyDeploying(buildId, uiAccountDescription(accountId)));
312
313
  await displayWarnLogs(accountId, projectConfig.name, buildId);
@@ -0,0 +1,7 @@
1
+ type PreviewResult = {
2
+ succeeded: boolean;
3
+ releaseTag?: string;
4
+ appId?: number;
5
+ };
6
+ export declare function triggerAndPollPreview(accountId: number, projectId: number, buildId: number, targetPortalId: number): Promise<PreviewResult>;
7
+ export {};
@@ -0,0 +1,48 @@
1
+ import { triggerAutoRelease, getAutoReleaseStatus, } from '../../api/releases.js';
2
+ import { PREVIEW_POLL_TIMEOUT } from '../constants.js';
3
+ import { poll } from '../polling.js';
4
+ import SpinniesManager from '../ui/SpinniesManager.js';
5
+ import { logError, ApiErrorContext } from '../errorHandlers/index.js';
6
+ import { lib } from '../../lang/en.js';
7
+ export async function triggerAndPollPreview(accountId, projectId, buildId, targetPortalId) {
8
+ let triggerResponse;
9
+ SpinniesManager.add('preview', {
10
+ text: lib.projectPreview.triggeringPreview(buildId, targetPortalId),
11
+ succeedColor: 'white',
12
+ });
13
+ try {
14
+ const { data } = await triggerAutoRelease(accountId, projectId, buildId, targetPortalId);
15
+ triggerResponse = data;
16
+ }
17
+ catch (e) {
18
+ SpinniesManager.fail('preview', {
19
+ text: lib.projectPreview.triggerFailed,
20
+ });
21
+ logError(e, new ApiErrorContext({
22
+ accountId,
23
+ request: 'preview trigger',
24
+ }));
25
+ return { succeeded: false };
26
+ }
27
+ const { releaseTag, appId } = triggerResponse;
28
+ SpinniesManager.update('preview', {
29
+ text: lib.projectPreview.pollingStatus(releaseTag, targetPortalId),
30
+ });
31
+ try {
32
+ await poll(() => getAutoReleaseStatus(accountId, projectId, targetPortalId, releaseTag, appId), { successStates: ['COMPLETE'], errorStates: [] }, PREVIEW_POLL_TIMEOUT);
33
+ }
34
+ catch (e) {
35
+ SpinniesManager.fail('preview', {
36
+ text: lib.projectPreview.pollFailed,
37
+ });
38
+ logError(e, new ApiErrorContext({
39
+ accountId,
40
+ request: 'preview status',
41
+ }));
42
+ return { succeeded: false, releaseTag, appId };
43
+ }
44
+ SpinniesManager.succeed('preview', {
45
+ text: lib.projectPreview.succeeded(releaseTag, targetPortalId),
46
+ });
47
+ return { succeeded: true, releaseTag, appId };
48
+ }
@@ -1,3 +1,4 @@
1
+ import { LoadedProjectConfig } from './config.js';
1
2
  export declare const REQUIRED_PACKAGES_AND_MIN_VERSIONS: {
2
3
  readonly eslint: "9.0.0";
3
4
  readonly '@eslint/js': "9.0.0";
@@ -23,6 +24,9 @@ export declare function hasEslintConfig(directory: string): boolean;
23
24
  export declare function hasDeprecatedEslintConfig(directory: string): boolean;
24
25
  export declare function getDeprecatedEslintConfigFiles(directory: string): string[];
25
26
  export declare function createEslintConfig(directory: string, platformVersion?: string | null): Promise<string>;
27
+ export declare function getUieLintablePackageJsonLocations(projectConfig: LoadedProjectConfig): Promise<string[]>;
28
+ export declare const HUBSPOT_UI_EXTENSIONS_RULE_PREFIX = "@hubspot/ui-extensions/";
29
+ export declare function isHubSpotEslintConfigActive(directory: string): Promise<boolean>;
26
30
  export declare function lintPackagesInDirectory(directory: string, projectDir?: string): Promise<{
27
31
  success: boolean;
28
32
  output: string;
@@ -10,7 +10,8 @@ import { uiLogger } from '../ui/logger.js';
10
10
  import { clearPackageJsonCache, safeGetPackageJsonCached, } from '../npm/packageJson.js';
11
11
  import { debugError } from '../errorHandlers/index.js';
12
12
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
13
- import { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, } from '../constants.js';
13
+ import { DEFAULT_PROJECT_TEMPLATE_BRANCH, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, } from '../constants.js';
14
+ import { CARDS_KEY, Components, PAGES_KEY, SETTINGS_KEY, } from '@hubspot/project-parsing-lib/constants';
14
15
  export const REQUIRED_PACKAGES_AND_MIN_VERSIONS = {
15
16
  eslint: '9.0.0',
16
17
  '@eslint/js': '9.0.0',
@@ -40,6 +41,11 @@ const DEPRECATED_ESLINT_CONFIG_FILES = [
40
41
  '.eslintrc.json',
41
42
  '.eslintrc',
42
43
  ];
44
+ const UIE_COMPONENTS = [
45
+ Components[CARDS_KEY],
46
+ Components[SETTINGS_KEY],
47
+ Components[PAGES_KEY],
48
+ ];
43
49
  export const LINT_SCRIPTS = {
44
50
  lint: 'eslint .',
45
51
  'lint:fix': 'eslint . --fix',
@@ -163,6 +169,35 @@ export async function createEslintConfig(directory, platformVersion) {
163
169
  throw error;
164
170
  }
165
171
  }
172
+ export async function getUieLintablePackageJsonLocations(projectConfig) {
173
+ if (!projectConfig.projectDir || !projectConfig.projectConfig?.srcDir) {
174
+ return [];
175
+ }
176
+ const srcDirAbsolute = path.resolve(projectConfig.projectDir, projectConfig.projectConfig.srcDir);
177
+ const uiePackageDirPrefixes = UIE_COMPONENTS.map(component => path.join(srcDirAbsolute, component.parentComponent
178
+ ? Components[component.parentComponent].dir
179
+ : '', component.dir));
180
+ const allLocations = await getProjectPackageJsonLocations(projectConfig.projectDir);
181
+ return allLocations.filter(location => {
182
+ const resolvedLocation = path.resolve(location);
183
+ return uiePackageDirPrefixes.some(prefix => resolvedLocation.startsWith(prefix));
184
+ });
185
+ }
186
+ export const HUBSPOT_UI_EXTENSIONS_RULE_PREFIX = '@hubspot/ui-extensions/';
187
+ export async function isHubSpotEslintConfigActive(directory) {
188
+ const exec = util.promisify(execAsync);
189
+ try {
190
+ const { stdout } = await exec('npx eslint --print-config ./Component.tsx', {
191
+ cwd: directory,
192
+ });
193
+ const config = JSON.parse(stdout);
194
+ const rules = config.rules ?? {};
195
+ return Object.keys(rules).some(rule => rule.startsWith(HUBSPOT_UI_EXTENSIONS_RULE_PREFIX));
196
+ }
197
+ catch {
198
+ return false;
199
+ }
200
+ }
166
201
  export async function lintPackagesInDirectory(directory, projectDir) {
167
202
  const displayPath = projectDir
168
203
  ? path.relative(projectDir, directory)
@@ -5,6 +5,7 @@ type ProjectUploadResult<T> = {
5
5
  result?: T;
6
6
  uploadError?: unknown;
7
7
  projectNotFound?: boolean;
8
+ projectId?: number;
8
9
  };
9
10
  type HandleProjectUploadArg<T> = {
10
11
  accountId: number;
@@ -16,9 +17,10 @@ type HandleProjectUploadArg<T> = {
16
17
  isUploadCommand?: boolean;
17
18
  sendIR?: boolean;
18
19
  skipValidation?: boolean;
20
+ skipNpmAudit?: boolean;
19
21
  profile?: string;
20
22
  };
21
- export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
23
+ export declare function handleProjectUpload<T>({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage, forceCreate, isUploadCommand, sendIR, skipValidation, skipNpmAudit, }: HandleProjectUploadArg<T>): Promise<ProjectUploadResult<T>>;
22
24
  export declare function validateSourceDirectory(srcDir: string, projectConfig: ProjectConfig, projectDir: string): Promise<void>;
23
25
  export declare function validateNoHSMetaMismatch(srcDir: string, projectConfig: ProjectConfig): Promise<void>;
24
26
  type HandleTranslateArg = {
@@ -18,6 +18,8 @@ import { walk } from '@hubspot/local-dev-lib/fs';
18
18
  import { LEGACY_CONFIG_FILES } from '../constants.js';
19
19
  import { archiveWorkspacesAndDependencies, getPackageJsonPathsToUpdate, getLockfilePathsToUpdate, } from './workspaces.js';
20
20
  import { isLegacyProject } from '@hubspot/project-parsing-lib/projects';
21
+ import { validateLintConfigOnUpload } from './validateLintConfigOnUpload.js';
22
+ import { runNpmAuditsBeforeProjectUpload } from './npmAuditOnUpload.js';
21
23
  async function uploadProjectFiles(accountId, projectName, filePath, uploadMessage, platformVersion, intermediateRepresentation) {
22
24
  const accountIdentifier = uiAccountDescription(accountId) || `${accountId}`;
23
25
  SpinniesManager.add('upload', {
@@ -44,7 +46,7 @@ async function uploadProjectFiles(accountId, projectName, filePath, uploadMessag
44
46
  }
45
47
  return { buildId, error };
46
48
  }
47
- export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, }) {
49
+ export async function handleProjectUpload({ accountId, projectConfig, projectDir, callbackFunc, profile, uploadMessage = '', forceCreate = false, isUploadCommand = false, sendIR = false, skipValidation = false, skipNpmAudit = false, }) {
48
50
  const srcDir = path.resolve(projectDir, projectConfig.srcDir);
49
51
  await validateSourceDirectory(srcDir, projectConfig, projectDir);
50
52
  await validateNoHSMetaMismatch(srcDir, projectConfig);
@@ -54,11 +56,28 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
54
56
  // Versions <= 2025.1 do not support the new npm workspaces bundling behavior.
55
57
  let workspaceMappings = [];
56
58
  let fileDependencyMappings = [];
59
+ let parsedPackageJsons = [];
57
60
  if (!isLegacyProject(projectConfig.platformVersion)) {
58
- const parsedPackageJsons = await findAndParsePackageJsonFiles(srcDir);
61
+ parsedPackageJsons = await findAndParsePackageJsonFiles(srcDir);
59
62
  workspaceMappings = await collectWorkspaceDirectories(parsedPackageJsons);
60
63
  fileDependencyMappings = await collectFileDependencies(parsedPackageJsons);
61
64
  }
65
+ if (isUploadCommand && !skipValidation) {
66
+ await validateLintConfigOnUpload({
67
+ srcDir,
68
+ projectDir,
69
+ parsedPackageJsons,
70
+ isLegacyPlatform: isLegacyProject(projectConfig.platformVersion),
71
+ });
72
+ }
73
+ if (isUploadCommand && !skipNpmAudit) {
74
+ await runNpmAuditsBeforeProjectUpload({
75
+ srcDir,
76
+ projectDir,
77
+ parsedPackageJsons,
78
+ isLegacyPlatform: isLegacyProject(projectConfig.platformVersion),
79
+ });
80
+ }
62
81
  const output = fs.createWriteStream(tempFile.name);
63
82
  const archive = archiver('zip');
64
83
  const result = new Promise((resolve, reject) => output.on('close', async function () {
@@ -79,7 +98,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
79
98
  return resolve({ uploadError: e });
80
99
  }
81
100
  }
82
- const { projectExists } = await ensureProjectExists(accountId, projectConfig.name, {
101
+ const { projectExists, project } = await ensureProjectExists(accountId, projectConfig.name, {
83
102
  forceCreate,
84
103
  uploadCommand: isUploadCommand,
85
104
  noLogs: true,
@@ -88,13 +107,14 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
88
107
  uiLogger.log(lib.projectUpload.handleProjectUpload.projectDoesNotExist(accountId));
89
108
  return resolve({ projectNotFound: true });
90
109
  }
110
+ const projectId = project?.id;
91
111
  const { buildId, error } = await uploadProjectFiles(accountId, projectConfig.name, tempFile.name, uploadMessage, projectConfig.platformVersion, intermediateRepresentation);
92
112
  if (error) {
93
- resolve({ uploadError: error });
113
+ resolve({ uploadError: error, projectId });
94
114
  }
95
115
  else if (callbackFunc) {
96
116
  const uploadResult = await callbackFunc(accountId, projectConfig, tempFile, buildId);
97
- resolve({ result: uploadResult });
117
+ resolve({ result: uploadResult, projectId });
98
118
  }
99
119
  }
100
120
  catch (e) {
@@ -123,7 +143,7 @@ export async function handleProjectUpload({ accountId, projectConfig, projectDir
123
143
  return ignored ? false : file;
124
144
  });
125
145
  // Archive workspaces and file: dependencies
126
- await archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings);
146
+ await archiveWorkspacesAndDependencies(archive, srcDir, workspaceMappings, fileDependencyMappings);
127
147
  archive.finalize();
128
148
  return result;
129
149
  }
@@ -0,0 +1,9 @@
1
+ import type { ParsedPackageJson } from '@hubspot/project-parsing-lib/workspaces';
2
+ type ValidateLintConfigOnUploadArgs = {
3
+ srcDir: string;
4
+ projectDir: string;
5
+ parsedPackageJsons: ParsedPackageJson[];
6
+ isLegacyPlatform: boolean;
7
+ };
8
+ export declare function validateLintConfigOnUpload({ srcDir, projectDir, parsedPackageJsons, isLegacyPlatform, }: ValidateLintConfigOnUploadArgs): Promise<void>;
9
+ export {};
@@ -0,0 +1,45 @@
1
+ import path from 'path';
2
+ import { lib } from '../../lang/en.js';
3
+ import { uiLogger } from '../ui/logger.js';
4
+ import { areAllLintPackagesInstalled, hasEslintConfig, isHubSpotEslintConfigActive, } from './uieLinting.js';
5
+ export async function validateLintConfigOnUpload({ srcDir, projectDir, parsedPackageJsons, isLegacyPlatform, }) {
6
+ const lintRoots = new Set();
7
+ if (isLegacyPlatform) {
8
+ lintRoots.add(srcDir);
9
+ }
10
+ else {
11
+ for (const { dir } of parsedPackageJsons) {
12
+ lintRoots.add(dir);
13
+ }
14
+ if (lintRoots.size === 0) {
15
+ lintRoots.add(srcDir);
16
+ }
17
+ }
18
+ let hasAnyOutput = false;
19
+ for (const lintRoot of lintRoots) {
20
+ const relativeRoot = path.relative(projectDir, lintRoot) || '.';
21
+ let warnMessage;
22
+ if (!areAllLintPackagesInstalled(lintRoot)) {
23
+ warnMessage =
24
+ lib.projectUpload.handleProjectUpload.lintPackagesNotConfigured(relativeRoot);
25
+ }
26
+ else if (!hasEslintConfig(lintRoot)) {
27
+ warnMessage =
28
+ lib.projectUpload.handleProjectUpload.lintConfigNotFound(relativeRoot);
29
+ }
30
+ else if (!(await isHubSpotEslintConfigActive(lintRoot))) {
31
+ warnMessage =
32
+ lib.projectUpload.handleProjectUpload.lintHubSpotRulesNotActive(relativeRoot);
33
+ }
34
+ if (warnMessage) {
35
+ if (!hasAnyOutput) {
36
+ uiLogger.log('');
37
+ hasAnyOutput = true;
38
+ }
39
+ uiLogger.warn(warnMessage);
40
+ }
41
+ }
42
+ if (hasAnyOutput) {
43
+ uiLogger.log('');
44
+ }
45
+ }
@@ -12,6 +12,16 @@ export type WorkspaceArchiveResult = {
12
12
  * Uses SHA256 truncated to 8 hex characters (4 billion possibilities).
13
13
  */
14
14
  export declare function shortHash(input: string): string;
15
+ /**
16
+ * Converts native path separators to POSIX forward slashes.
17
+ *
18
+ * Zip entry names and npm workspace globs are POSIX-only. On Windows,
19
+ * `path.relative` returns backslash-separated paths; archiver normalizes
20
+ * its appended entry names to forward slashes but its filter callback
21
+ * receives forward-slashed names too. Without this normalization, lookups
22
+ * in our exclusion Sets miss on Windows and a file gets archived twice.
23
+ */
24
+ export declare function toPosixPath(p: string): string;
15
25
  /**
16
26
  * Determines the archive path for an external workspace or file: dependency.
17
27
  * Produces `_workspaces/<basename>-<hash>` with no subdirectory.
@@ -39,4 +49,4 @@ export declare function getLockfilePathsToUpdate(srcDir: string, workspaceMappin
39
49
  * Main orchestration function that handles archiving of workspaces and file dependencies.
40
50
  * This is the clean integration point for upload.ts.
41
51
  */
42
- export declare function archiveWorkspacesAndDependencies(archive: archiver.Archiver, srcDir: string, projectDir: string, workspaceMappings: WorkspaceMapping[], fileDependencyMappings: FileDependencyMapping[]): Promise<WorkspaceArchiveResult>;
52
+ export declare function archiveWorkspacesAndDependencies(archive: archiver.Archiver, srcDir: string, workspaceMappings: WorkspaceMapping[], fileDependencyMappings: FileDependencyMapping[]): Promise<WorkspaceArchiveResult>;
@@ -12,6 +12,21 @@ import { lib } from '../../lang/en.js';
12
12
  export function shortHash(input) {
13
13
  return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
14
14
  }
15
+ /**
16
+ * Converts native path separators to POSIX forward slashes.
17
+ *
18
+ * Zip entry names and npm workspace globs are POSIX-only. On Windows,
19
+ * `path.relative` returns backslash-separated paths; archiver normalizes
20
+ * its appended entry names to forward slashes but its filter callback
21
+ * receives forward-slashed names too. Without this normalization, lookups
22
+ * in our exclusion Sets miss on Windows and a file gets archived twice.
23
+ */
24
+ export function toPosixPath(p) {
25
+ if (path.sep === path.posix.sep) {
26
+ return p;
27
+ }
28
+ return p.replaceAll(path.sep, path.posix.sep);
29
+ }
15
30
  /**
16
31
  * Determines the archive path for an external workspace or file: dependency.
17
32
  * Produces `_workspaces/<basename>-<hash>` with no subdirectory.
@@ -20,7 +35,7 @@ export function shortHash(input) {
20
35
  export function computeExternalArchivePath(absolutePath) {
21
36
  const resolved = path.resolve(absolutePath);
22
37
  const name = path.basename(resolved);
23
- return path.join('_workspaces', `${name}-${shortHash(resolved)}`);
38
+ return path.posix.join('_workspaces', `${name}-${shortHash(resolved)}`);
24
39
  }
25
40
  /**
26
41
  * Returns true if dir is inside srcDir (i.e. it will already be included
@@ -71,7 +86,7 @@ async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
71
86
  if (isInsideSrcDir(workspaceDir, srcDir)) {
72
87
  // Internal: already in archive from srcDir walk.
73
88
  // Store the relative path from the package.json directory so npm can resolve it.
74
- const relPath = path.relative(path.dirname(sourcePackageJsonPath), path.resolve(workspaceDir));
89
+ const relPath = toPosixPath(path.relative(path.dirname(sourcePackageJsonPath), path.resolve(workspaceDir)));
75
90
  packageWorkspaceEntries.get(sourcePackageJsonPath).push(relPath);
76
91
  }
77
92
  else {
@@ -89,7 +104,7 @@ async function archiveWorkspaceDirectories(archive, srcDir, workspaceMappings) {
89
104
  externalsToArchive.push({ dir: workspaceDir, archivePath });
90
105
  }
91
106
  const relPkgJsonDir = path.relative(srcDir, path.dirname(sourcePackageJsonPath));
92
- const relativeEntry = path.relative(relPkgJsonDir, archivePath);
107
+ const relativeEntry = toPosixPath(path.relative(relPkgJsonDir, archivePath));
93
108
  packageWorkspaceEntries.get(sourcePackageJsonPath).push(relativeEntry);
94
109
  }
95
110
  }
@@ -130,7 +145,7 @@ async function archiveFileDependencies(archive, srcDir, fileDependencyMappings,
130
145
  packageFileDeps.set(sourcePackageJsonPath, new Map());
131
146
  }
132
147
  const relPkgJsonDir = path.relative(srcDir, path.dirname(sourcePackageJsonPath));
133
- const relativeArchivePath = path.relative(relPkgJsonDir, archivePath);
148
+ const relativeArchivePath = toPosixPath(path.relative(relPkgJsonDir, archivePath));
134
149
  packageFileDeps
135
150
  .get(sourcePackageJsonPath)
136
151
  .set(packageName, relativeArchivePath);
@@ -172,7 +187,7 @@ export async function updatePackageJsonInArchive(archive, srcDir, packageWorkspa
172
187
  if (!fs.existsSync(packageJsonPath)) {
173
188
  continue;
174
189
  }
175
- const relativePackageJsonPath = path.relative(srcDir, packageJsonPath);
190
+ const relativePackageJsonPath = toPosixPath(path.relative(srcDir, packageJsonPath));
176
191
  let rawContent;
177
192
  try {
178
193
  rawContent = fs.readFileSync(packageJsonPath, 'utf8');
@@ -261,11 +276,11 @@ export function rewriteLockfileForExternalDeps(lockfileContent, pathMappings) {
261
276
  export function getPackageJsonPathsToUpdate(srcDir, workspaceMappings, fileDependencyMappings) {
262
277
  const paths = new Set();
263
278
  for (const { sourcePackageJsonPath } of workspaceMappings) {
264
- paths.add(path.relative(srcDir, sourcePackageJsonPath));
279
+ paths.add(toPosixPath(path.relative(srcDir, sourcePackageJsonPath)));
265
280
  }
266
281
  for (const { localPath, sourcePackageJsonPath } of fileDependencyMappings) {
267
282
  if (!isInsideSrcDir(localPath, srcDir)) {
268
- paths.add(path.relative(srcDir, sourcePackageJsonPath));
283
+ paths.add(toPosixPath(path.relative(srcDir, sourcePackageJsonPath)));
269
284
  }
270
285
  }
271
286
  return paths;
@@ -290,7 +305,7 @@ export function getLockfilePathsToUpdate(srcDir, workspaceMappings, fileDependen
290
305
  for (const dir of dirsWithExternalDeps) {
291
306
  const lockfilePath = path.join(dir, 'package-lock.json');
292
307
  if (fs.existsSync(lockfilePath)) {
293
- paths.add(path.relative(srcDir, lockfilePath));
308
+ paths.add(toPosixPath(path.relative(srcDir, lockfilePath)));
294
309
  }
295
310
  }
296
311
  return paths;
@@ -319,12 +334,12 @@ async function rewriteLockfilesInArchive(archive, srcDir, externalArchivePaths,
319
334
  const pathMappings = [];
320
335
  for (const [absoluteExternalPath, archivePath] of externalArchivePaths) {
321
336
  pathMappings.push({
322
- oldPath: path.relative(dir, absoluteExternalPath),
323
- newPath: path.relative(dir, path.join(srcDir, archivePath)),
337
+ oldPath: toPosixPath(path.relative(dir, absoluteExternalPath)),
338
+ newPath: toPosixPath(path.relative(dir, path.join(srcDir, archivePath))),
324
339
  });
325
340
  }
326
341
  const rewritten = rewriteLockfileForExternalDeps(lockfileContent, pathMappings);
327
- const relativeLockfilePath = path.relative(srcDir, lockfilePath);
342
+ const relativeLockfilePath = toPosixPath(path.relative(srcDir, lockfilePath));
328
343
  uiLogger.debug(lib.projectUpload.handleProjectUpload.updatingLockfile(relativeLockfilePath));
329
344
  archive.append(JSON.stringify(rewritten, null, 2), {
330
345
  name: relativeLockfilePath,
@@ -336,7 +351,7 @@ async function rewriteLockfilesInArchive(archive, srcDir, externalArchivePaths,
336
351
  * Main orchestration function that handles archiving of workspaces and file dependencies.
337
352
  * This is the clean integration point for upload.ts.
338
353
  */
339
- export async function archiveWorkspacesAndDependencies(archive, srcDir, projectDir, workspaceMappings, fileDependencyMappings) {
354
+ export async function archiveWorkspacesAndDependencies(archive, srcDir, workspaceMappings, fileDependencyMappings) {
340
355
  // Archive workspace directories (internal ones are skipped, externals are copied)
341
356
  const { externalArchivePaths, packageWorkspaceEntries } = await archiveWorkspaceDirectories(archive, srcDir, workspaceMappings);
342
357
  // Archive external file: dependencies (internals are skipped)
@@ -1,29 +1,19 @@
1
- export type UsageTrackingMeta = {
2
- action?: string;
3
- os?: string;
4
- nodeVersion?: string;
5
- nodeMajorVersion?: string;
6
- version?: string;
7
- command?: string;
8
- authType?: string;
9
- step?: string;
10
- assetType?: string;
11
- mode?: string;
12
- type?: string | number;
13
- file?: boolean;
14
- successful?: boolean;
15
- };
1
+ import { type UsageTrackingMeta, type ConfigType, type ExecutionSource } from './api/usageTracking.js';
2
+ export type { UsageTrackingMeta, ConfigType, ExecutionSource, } from './api/usageTracking.js';
16
3
  export declare const EventClass: {
17
4
  USAGE: string;
18
5
  INTERACTION: string;
19
6
  VIEW: string;
20
7
  ACTIVATION: string;
21
8
  };
22
- export declare function getNodeVersionData(): {
9
+ export declare function getExecutionEnvironmentMeta(): {
10
+ os: string;
23
11
  nodeVersion: string;
24
12
  nodeMajorVersion: string;
13
+ version: string;
14
+ configType?: ConfigType;
15
+ executionSource: ExecutionSource;
25
16
  };
26
- export declare function getPlatform(): string;
27
17
  export declare function trackCommandUsage(command: string, meta?: UsageTrackingMeta, accountId?: number): Promise<void>;
28
18
  export declare function trackHelpUsage(command: string): Promise<void>;
29
19
  export declare function trackConvertFieldsUsage(command: string): Promise<void>;