@hubspot/cli 8.0.11-experimental.2 → 8.0.12-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/bin/cli.js +4 -3
  2. package/commands/account/clean.js +2 -0
  3. package/commands/account/createOverride.js +3 -0
  4. package/commands/account/info.js +34 -16
  5. package/commands/account/link.d.ts +4 -0
  6. package/commands/account/link.js +89 -0
  7. package/commands/account/list.js +29 -71
  8. package/commands/account/remove.js +2 -0
  9. package/commands/account/removeOverride.js +3 -0
  10. package/commands/account/unlink.d.ts +4 -0
  11. package/commands/account/unlink.js +70 -0
  12. package/commands/account/use.js +71 -1
  13. package/commands/account.js +4 -0
  14. package/commands/project/appInstallStatus.d.ts +4 -0
  15. package/commands/project/appInstallStatus.js +132 -0
  16. package/commands/project/create.js +8 -0
  17. package/commands/project/dev/deprecatedFlow.js +20 -2
  18. package/commands/project/dev/index.js +6 -0
  19. package/commands/project/dev/unifiedFlow.js +20 -3
  20. package/commands/project/lint.js +20 -2
  21. package/commands/project/upload.d.ts +2 -0
  22. package/commands/project/upload.js +47 -3
  23. package/commands/project.js +2 -0
  24. package/lang/en.d.ts +122 -0
  25. package/lang/en.js +136 -8
  26. package/lib/app/migrate.js +2 -1
  27. package/lib/constants.d.ts +2 -0
  28. package/lib/constants.js +4 -0
  29. package/lib/doctor/Doctor.js +5 -5
  30. package/lib/link/accountTableUtils.d.ts +10 -0
  31. package/lib/link/accountTableUtils.js +39 -0
  32. package/lib/link/index.d.ts +18 -0
  33. package/lib/link/index.js +185 -0
  34. package/lib/link/linkUtils.d.ts +5 -0
  35. package/lib/link/linkUtils.js +49 -0
  36. package/lib/link/prompts.d.ts +7 -0
  37. package/lib/link/prompts.js +126 -0
  38. package/lib/link/renderLinkedAccountsTable.d.ts +2 -0
  39. package/lib/link/renderLinkedAccountsTable.js +14 -0
  40. package/lib/link/warnIfLinkedDirectory.d.ts +1 -0
  41. package/lib/link/warnIfLinkedDirectory.js +9 -0
  42. package/lib/projects/ProjectLogsManager.js +4 -1
  43. package/lib/projects/localDev/DevServerManager_DEPRECATED.d.ts +2 -1
  44. package/lib/projects/localDev/DevServerManager_DEPRECATED.js +2 -2
  45. package/lib/projects/localDev/LocalDevManager_DEPRECATED.d.ts +2 -0
  46. package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +3 -0
  47. package/lib/projects/preview.d.ts +7 -0
  48. package/lib/projects/preview.js +58 -0
  49. package/lib/projects/uieLinting.d.ts +17 -3
  50. package/lib/projects/uieLinting.js +93 -28
  51. package/lib/projects/upload.d.ts +1 -0
  52. package/lib/projects/upload.js +4 -3
  53. package/lib/prompts/projectsLogsPrompt.js +3 -0
  54. package/lib/prompts/promptUtils.js +1 -0
  55. package/lib/ui/accountTable.d.ts +8 -0
  56. package/lib/ui/accountTable.js +67 -0
  57. package/lib/yargs/parseYargsOrExit.d.ts +4 -0
  58. package/lib/yargs/parseYargsOrExit.js +25 -0
  59. package/mcp-server/server.js +39 -1
  60. package/mcp-server/tools/index.js +2 -0
  61. package/mcp-server/tools/project/AddFeatureToProjectTool.js +1 -1
  62. package/mcp-server/tools/project/CreateTestAccountTool.js +1 -1
  63. package/mcp-server/tools/project/DeployProjectTool.js +1 -1
  64. package/mcp-server/tools/project/FindProjectsTool.d.ts +15 -0
  65. package/mcp-server/tools/project/FindProjectsTool.js +60 -0
  66. package/mcp-server/tools/project/GetBuildLogsTool.js +1 -1
  67. package/mcp-server/tools/project/GetBuildStatusTool.js +1 -1
  68. package/mcp-server/tools/project/UploadProjectTools.js +1 -1
  69. package/mcp-server/tools/project/ValidateProjectTool.js +1 -1
  70. package/package.json +7 -7
  71. package/types/Link.d.ts +32 -0
  72. package/types/Link.js +5 -0
  73. package/types/PackageJson.d.ts +1 -0
  74. package/types/Prompts.d.ts +1 -0
  75. package/types/Yargs.d.ts +1 -0
@@ -0,0 +1,185 @@
1
+ import { getAllConfigAccounts, getConfigDefaultAccountIfExists, } from '@hubspot/local-dev-lib/config';
2
+ import { confirmPrompt } from '../prompts/promptUtils.js';
3
+ import { promptForAccountsToLink, promptForAccountsToUnlink, promptForAction, promptForDefaultAccount, } from './prompts.js';
4
+ import { ACTION_RESULT_STATUS, } from '../../types/Link.js';
5
+ import { getDefaultAccountOverrideFilePath, removeDefaultAccountOverrideFile, } from '@hubspot/local-dev-lib/config/defaultAccountOverride';
6
+ import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments';
7
+ import { authenticateNewAccount } from '../accountAuth.js';
8
+ import { commands } from '../../lang/en.js';
9
+ import { uiLogger } from '../ui/logger.js';
10
+ export class ActionHandlers {
11
+ static async link({ state, context, }) {
12
+ const { eligibleAccounts, inEligibleAccounts } = context.globalAccountsList.reduce((accumulator, account) => {
13
+ if (state.accounts.includes(account.accountId)) {
14
+ accumulator.inEligibleAccounts.push(account);
15
+ }
16
+ else {
17
+ accumulator.eligibleAccounts.push(account);
18
+ }
19
+ return accumulator;
20
+ }, { eligibleAccounts: [], inEligibleAccounts: [] });
21
+ const toAdd = await promptForAccountsToLink(context, eligibleAccounts, inEligibleAccounts, state.localDefaultAccount);
22
+ const accounts = [...state.accounts, ...toAdd];
23
+ uiLogger.info(commands.account.subcommands.link.events.accountsLinked(toAdd.length));
24
+ const overrideFilePath = getDefaultAccountOverrideFilePath();
25
+ if (overrideFilePath &&
26
+ context.accountOverrideId &&
27
+ accounts.includes(context.accountOverrideId)) {
28
+ uiLogger.log(commands.account.subcommands.link.events.overrideAccountDetected(context.accountOverrideId));
29
+ uiLogger.log('');
30
+ const useOverride = await confirmPrompt(commands.account.subcommands.link.prompts.keepAsDefault);
31
+ removeDefaultAccountOverrideFile();
32
+ uiLogger.success(commands.account.subcommands.link.events.overrideFileRemoved);
33
+ if (useOverride) {
34
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(context.accountOverrideId));
35
+ return {
36
+ status: ACTION_RESULT_STATUS.SUCCESS,
37
+ settings: {
38
+ accounts,
39
+ localDefaultAccount: context.accountOverrideId,
40
+ },
41
+ };
42
+ }
43
+ }
44
+ const localDefaultAccount = await resolveDefaultAccount({
45
+ accounts,
46
+ currentDefault: state.localDefaultAccount,
47
+ });
48
+ return {
49
+ status: ACTION_RESULT_STATUS.SUCCESS,
50
+ settings: { accounts, localDefaultAccount },
51
+ };
52
+ }
53
+ static async unlink({ state }) {
54
+ const toRemove = await promptForAccountsToUnlink(state.accounts, state.localDefaultAccount);
55
+ if (toRemove.length === 0) {
56
+ return { status: ACTION_RESULT_STATUS.NOOP };
57
+ }
58
+ const remainingAccounts = state.accounts.filter(account => !toRemove.includes(account));
59
+ const defaultWasRemoved = state.localDefaultAccount !== undefined &&
60
+ toRemove.includes(state.localDefaultAccount);
61
+ // All accounts removed
62
+ if (remainingAccounts.length === 0) {
63
+ uiLogger.success(commands.account.subcommands.link.events.noAccountsLinked);
64
+ return {
65
+ status: ACTION_RESULT_STATUS.SUCCESS,
66
+ settings: {
67
+ accounts: remainingAccounts,
68
+ localDefaultAccount: undefined,
69
+ },
70
+ };
71
+ }
72
+ // Default was NOT removed — accounts remain, default unchanged
73
+ if (!defaultWasRemoved) {
74
+ uiLogger.success(commands.account.subcommands.link.events.accountsUnlinked(toRemove.length));
75
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountRemains(state.localDefaultAccount));
76
+ return {
77
+ status: ACTION_RESULT_STATUS.SUCCESS,
78
+ settings: {
79
+ accounts: remainingAccounts,
80
+ localDefaultAccount: state.localDefaultAccount,
81
+ },
82
+ };
83
+ }
84
+ // Default WAS removed — need a new default
85
+ uiLogger.warn(commands.account.subcommands.link.events.defaultAccountRemoved(remainingAccounts.length !== 1));
86
+ const localDefaultAccount = await resolveDefaultAccount({
87
+ accounts: remainingAccounts,
88
+ currentDefault: undefined,
89
+ });
90
+ if (remainingAccounts.length === 1) {
91
+ uiLogger.success(commands.account.subcommands.link.events.updatedLinkedAccounts);
92
+ }
93
+ return {
94
+ status: ACTION_RESULT_STATUS.SUCCESS,
95
+ settings: { accounts: remainingAccounts, localDefaultAccount },
96
+ };
97
+ }
98
+ static async authenticate({ state, context, args, }) {
99
+ const updatedConfig = await authenticateNewAccount({
100
+ env: args.qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD,
101
+ setAsDefaultAccount: false,
102
+ });
103
+ if (!updatedConfig) {
104
+ return {
105
+ status: ACTION_RESULT_STATUS.ERROR,
106
+ reason: commands.account.subcommands.link.errors.authFailed,
107
+ };
108
+ }
109
+ const updatedContext = {
110
+ globalAccountsList: getAllConfigAccounts(),
111
+ globalDefaultAccount: getConfigDefaultAccountIfExists(),
112
+ accountOverrideId: context.accountOverrideId,
113
+ preselectedAccountId: updatedConfig.accountId,
114
+ };
115
+ return ActionHandlers.link({ state, context: updatedContext, args });
116
+ }
117
+ static async cancel() {
118
+ return {
119
+ status: ACTION_RESULT_STATUS.NOOP,
120
+ };
121
+ }
122
+ }
123
+ export async function handleLinkFlow({ settings, accountOverrideId, args, }) {
124
+ const context = {
125
+ globalAccountsList: getAllConfigAccounts(),
126
+ globalDefaultAccount: getConfigDefaultAccountIfExists(),
127
+ accountOverrideId,
128
+ };
129
+ const accounts = settings.accounts ?? [];
130
+ // The default account must be one of the linked accounts. This can get
131
+ // out of sync if the settings file is manually edited.
132
+ const hasInvalidDefault = settings.localDefaultAccount !== undefined &&
133
+ !accounts.includes(settings.localDefaultAccount);
134
+ if (hasInvalidDefault) {
135
+ uiLogger.warn(commands.account.subcommands.link.events.invalidDefaultAccount(settings.localDefaultAccount));
136
+ }
137
+ const initialState = {
138
+ localDefaultAccount: hasInvalidDefault
139
+ ? undefined
140
+ : settings.localDefaultAccount,
141
+ accounts,
142
+ };
143
+ return runAction(initialState, context, args);
144
+ }
145
+ async function runAction(state, context, args) {
146
+ const action = await promptForAction(state);
147
+ return ActionHandlers[action]({ state, context, args });
148
+ }
149
+ async function resolveDefaultAccount({ accounts, currentDefault, prompt = '', }) {
150
+ if (accounts.length === 1) {
151
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(accounts[0]));
152
+ return accounts[0];
153
+ }
154
+ return promptForDefaultAccount(accounts, currentDefault, prompt);
155
+ }
156
+ export async function handleLinkedUseAction({ state, targetAccountId, }) {
157
+ if (targetAccountId !== undefined) {
158
+ if (state.accounts.includes(targetAccountId)) {
159
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(targetAccountId));
160
+ return {
161
+ status: ACTION_RESULT_STATUS.SUCCESS,
162
+ settings: {
163
+ accounts: state.accounts,
164
+ localDefaultAccount: targetAccountId,
165
+ },
166
+ };
167
+ }
168
+ const accounts = [...state.accounts, targetAccountId];
169
+ uiLogger.info(commands.account.subcommands.link.events.accountsLinked(1));
170
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(targetAccountId));
171
+ return {
172
+ status: ACTION_RESULT_STATUS.SUCCESS,
173
+ settings: { accounts, localDefaultAccount: targetAccountId },
174
+ };
175
+ }
176
+ const localDefaultAccount = await resolveDefaultAccount({
177
+ accounts: state.accounts,
178
+ currentDefault: state.localDefaultAccount,
179
+ });
180
+ uiLogger.success(commands.account.subcommands.link.events.defaultAccountSet(localDefaultAccount));
181
+ return {
182
+ status: ACTION_RESULT_STATUS.SUCCESS,
183
+ settings: { accounts: state.accounts, localDefaultAccount },
184
+ };
185
+ }
@@ -0,0 +1,5 @@
1
+ import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
2
+ export declare function isDirectoryLinked(settings: HsSettingsFile | null): settings is HsSettingsFile;
3
+ export declare function hasDeprecatedConfigConflict(commandArgs: (string | number)[]): boolean;
4
+ export declare function addAccountToLinkedSettings(accountId: number): void;
5
+ export declare function writeLinkedSettings(settings: HsSettingsFile, settingsPath: string): boolean;
@@ -0,0 +1,49 @@
1
+ import { localConfigFileExists } from '@hubspot/local-dev-lib/config';
2
+ import { getHsSettingsFileIfExists, writeHsSettingsFile, } from '@hubspot/local-dev-lib/config/hsSettings';
3
+ import { uiLogger } from '../ui/logger.js';
4
+ import { debugError } from '../errorHandlers/index.js';
5
+ import { commands } from '../../lang/en.js';
6
+ export function isDirectoryLinked(settings) {
7
+ return settings !== null && settings.accounts.length > 0;
8
+ }
9
+ export function hasDeprecatedConfigConflict(commandArgs) {
10
+ if (localConfigFileExists()) {
11
+ uiLogger.error(commands.account.subcommands.link.shared.deprecatedConfigNotSupported(`hs ${commandArgs.join(' ')}`));
12
+ return true;
13
+ }
14
+ return false;
15
+ }
16
+ export function addAccountToLinkedSettings(accountId) {
17
+ if (localConfigFileExists()) {
18
+ return;
19
+ }
20
+ const settings = getHsSettingsFileIfExists();
21
+ if (!settings || settings.accounts.length === 0) {
22
+ return;
23
+ }
24
+ if (settings.accounts.includes(accountId)) {
25
+ return;
26
+ }
27
+ const updated = {
28
+ ...settings,
29
+ accounts: [...settings.accounts, accountId],
30
+ };
31
+ try {
32
+ writeHsSettingsFile(updated);
33
+ uiLogger.info(commands.account.subcommands.link.shared.accountAutoLinked(accountId));
34
+ }
35
+ catch (err) {
36
+ uiLogger.warn(commands.account.subcommands.link.shared.accountAutoLinkFailed(accountId));
37
+ debugError(err);
38
+ }
39
+ }
40
+ export function writeLinkedSettings(settings, settingsPath) {
41
+ try {
42
+ writeHsSettingsFile(settings);
43
+ return true;
44
+ }
45
+ catch (err) {
46
+ uiLogger.error(commands.account.subcommands.link.shared.writeSettingsFailed(settingsPath, err));
47
+ return false;
48
+ }
49
+ }
@@ -0,0 +1,7 @@
1
+ import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
+ import { ActionName, LinkContext } from '../../types/Link.js';
3
+ import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
4
+ export declare function promptForAction(state: HsSettingsFile): Promise<ActionName>;
5
+ export declare function promptForDefaultAccount(accounts: number[], currentDefaultAccount: number | undefined, prompt?: string): Promise<number>;
6
+ export declare function promptForAccountsToLink(context: LinkContext, eligibleAccounts: HubSpotConfigAccount[], inEligibleAccounts: HubSpotConfigAccount[], localDefaultAccount: number | undefined): Promise<number[]>;
7
+ export declare function promptForAccountsToUnlink(accounts: number[], localDefaultAccount: number | undefined): Promise<number[]>;
@@ -0,0 +1,126 @@
1
+ import { promptUser } from '../prompts/promptUtils.js';
2
+ import { uiAccountDescription } from '../ui/index.js';
3
+ import { uiLogger } from '../ui/logger.js';
4
+ import { commands } from '../../lang/en.js';
5
+ import { buildAccountRow, getNameColumnWidth, buildAccountHeader, sortDefaultFirst, } from './accountTableUtils.js';
6
+ import { Separator } from '@inquirer/prompts';
7
+ function buildColumnarChoices(accounts, localDefaultAccount) {
8
+ const rows = accounts.map(a => ({
9
+ ...buildAccountRow(a.accountId, a.accountId === localDefaultAccount),
10
+ disabled: a.disabled,
11
+ checked: a.checked,
12
+ hint: a.hint,
13
+ }));
14
+ const nameWidth = getNameColumnWidth(rows);
15
+ const header = buildAccountHeader(nameWidth);
16
+ return [
17
+ new Separator(header),
18
+ ...rows.map(row => {
19
+ const label = `${row.name.padEnd(nameWidth)} ${row.accountId}`;
20
+ return {
21
+ name: row.hint ? `${label} ${row.hint}` : label,
22
+ short: uiAccountDescription(Number(row.accountId), false),
23
+ value: Number(row.accountId),
24
+ disabled: row.disabled,
25
+ checked: row.checked,
26
+ };
27
+ }),
28
+ ];
29
+ }
30
+ function mapLinkAccountChoices(eligibleAccounts, inEligibleAccounts, accountOverrideId, localDefaultAccount, preselectedAccountId) {
31
+ const sortedIneligible = sortDefaultFirst(inEligibleAccounts, localDefaultAccount);
32
+ const accounts = [
33
+ ...eligibleAccounts.map(a => ({
34
+ accountId: a.accountId,
35
+ disabled: false,
36
+ checked: a.accountId === accountOverrideId ||
37
+ a.accountId === preselectedAccountId,
38
+ hint: a.accountId === accountOverrideId
39
+ ? commands.account.subcommands.link.prompts.fromHsAccount
40
+ : a.accountId === preselectedAccountId
41
+ ? commands.account.subcommands.link.prompts.newlyAuthenticated
42
+ : undefined,
43
+ })),
44
+ ...sortedIneligible.map(a => ({
45
+ accountId: a.accountId,
46
+ disabled: commands.account.subcommands.link.prompts.alreadyLinked,
47
+ checked: false,
48
+ })),
49
+ ];
50
+ return buildColumnarChoices(accounts, localDefaultAccount);
51
+ }
52
+ export async function promptForAction(state) {
53
+ const isSettingsEmpty = state.accounts.length === 0 && state.localDefaultAccount === undefined;
54
+ const { accountEditOption } = await promptUser({
55
+ type: 'list',
56
+ name: 'accountEditOption',
57
+ message: isSettingsEmpty
58
+ ? commands.account.subcommands.link.prompts.howToProceed
59
+ : commands.account.subcommands.link.prompts.whatToDo,
60
+ choices: [
61
+ {
62
+ name: commands.account.subcommands.link.prompts.linkExisting,
63
+ value: 'link',
64
+ },
65
+ {
66
+ name: commands.account.subcommands.link.prompts.authenticateNew,
67
+ value: 'authenticate',
68
+ },
69
+ {
70
+ name: commands.account.subcommands.link.prompts.cancel,
71
+ value: 'cancel',
72
+ },
73
+ ],
74
+ });
75
+ uiLogger.log('');
76
+ return accountEditOption;
77
+ }
78
+ export async function promptForDefaultAccount(accounts, currentDefaultAccount, prompt = '') {
79
+ const choiceAccounts = accounts.map(accountId => ({
80
+ accountId,
81
+ }));
82
+ const choices = buildColumnarChoices(choiceAccounts, currentDefaultAccount);
83
+ const { defaultAccount } = await promptUser({
84
+ type: 'list',
85
+ name: 'defaultAccount',
86
+ pageSize: 20,
87
+ message: prompt || commands.account.subcommands.link.prompts.selectDefault,
88
+ choices,
89
+ default: currentDefaultAccount ?? undefined,
90
+ });
91
+ uiLogger.log('');
92
+ return defaultAccount;
93
+ }
94
+ export async function promptForAccountsToLink(context, eligibleAccounts, inEligibleAccounts, localDefaultAccount) {
95
+ const { accountsToAdd } = await promptUser({
96
+ type: 'checkbox',
97
+ name: 'accountsToAdd',
98
+ pageSize: 20,
99
+ message: commands.account.subcommands.link.prompts.selectToLink,
100
+ choices: mapLinkAccountChoices(eligibleAccounts, inEligibleAccounts, context.accountOverrideId, localDefaultAccount, context.preselectedAccountId),
101
+ validate: (answer) => {
102
+ if (answer.length === 0) {
103
+ return commands.account.subcommands.link.prompts.mustSelectOne;
104
+ }
105
+ return true;
106
+ },
107
+ });
108
+ uiLogger.log('');
109
+ return accountsToAdd;
110
+ }
111
+ export async function promptForAccountsToUnlink(accounts, localDefaultAccount) {
112
+ const sortedAccounts = sortDefaultFirst(accounts, localDefaultAccount);
113
+ const choiceAccounts = sortedAccounts.map(accountId => ({
114
+ accountId,
115
+ }));
116
+ const choices = buildColumnarChoices(choiceAccounts, localDefaultAccount);
117
+ const { accountsToRemove } = await promptUser({
118
+ type: 'checkbox',
119
+ name: 'accountsToRemove',
120
+ pageSize: 20,
121
+ message: commands.account.subcommands.link.prompts.selectToUnlink,
122
+ choices,
123
+ });
124
+ uiLogger.log('');
125
+ return accountsToRemove;
126
+ }
@@ -0,0 +1,2 @@
1
+ import { HsSettingsFile } from '@hubspot/local-dev-lib/types/HsSettings';
2
+ export declare function renderLinkedAccountsTable(settings: HsSettingsFile): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { commands } from '../../lang/en.js';
2
+ import { renderTable } from '../../ui/render.js';
3
+ import { buildAccountRow, sortDefaultFirst } from './accountTableUtils.js';
4
+ export async function renderLinkedAccountsTable(settings) {
5
+ const labels = commands.account.subcommands.list.labels;
6
+ const tableHeader = [labels.name, labels.accountId];
7
+ const sortedAccounts = sortDefaultFirst(settings.accounts, settings.localDefaultAccount);
8
+ const tableData = sortedAccounts.map(accountId => {
9
+ const isDefault = accountId === settings.localDefaultAccount;
10
+ const row = buildAccountRow(accountId, isDefault);
11
+ return [row.name, row.accountId];
12
+ });
13
+ await renderTable(tableHeader, tableData, true);
14
+ }
@@ -0,0 +1 @@
1
+ export declare function warnIfLinkedDirectory(args: (string | number)[]): void;
@@ -0,0 +1,9 @@
1
+ import { getHsSettingsFilePath } from '@hubspot/local-dev-lib/config/hsSettings';
2
+ import { uiLogger } from '../ui/logger.js';
3
+ import { lib } from '../../lang/en.js';
4
+ export function warnIfLinkedDirectory(args) {
5
+ if (getHsSettingsFilePath() === null) {
6
+ return;
7
+ }
8
+ uiLogger.warn(lib.linkedDirectory.warning(`hs ${args.join(' ')}`, getHsSettingsFilePath()));
9
+ }
@@ -134,9 +134,12 @@ class _ProjectLogsManager {
134
134
  return this.functions.map(serverlessFunction => serverlessFunction.componentName);
135
135
  }
136
136
  setFunction(functionName) {
137
- if (!functionName || this.functions.length === 0) {
137
+ if (this.functions.length === 0) {
138
138
  throw new Error(commands.project.logs.errors.noFunctionsInProject);
139
139
  }
140
+ if (!functionName) {
141
+ throw new Error(commands.project.logs.errors.functionNameRequired);
142
+ }
140
143
  this.selectedFunction = this.functions.find(serverlessFunction => serverlessFunction.componentName === functionName);
141
144
  if (!this.selectedFunction) {
142
145
  throw new Error(commands.project.logs.errors.noFunctionWithName(functionName));
@@ -21,12 +21,13 @@ declare class DevServerManager_DEPRECATED {
21
21
  [key: string]: Component;
22
22
  }) => Promise<void>): Promise<void>;
23
23
  arrangeComponentsByType(components: Component[]): ComponentsByType;
24
- setup({ components, onUploadRequired, accountId, setActiveApp, exit, }: {
24
+ setup({ components, onUploadRequired, accountId, setActiveApp, exit, port, }: {
25
25
  components: Component[];
26
26
  onUploadRequired: () => void;
27
27
  accountId: number;
28
28
  setActiveApp: (appUid: string | undefined) => Promise<void>;
29
29
  exit: ExitFunction;
30
+ port?: number;
30
31
  }): Promise<void>;
31
32
  start({ accountId, projectConfig, }: {
32
33
  accountId: number;
@@ -57,7 +57,7 @@ class DevServerManager_DEPRECATED {
57
57
  return acc;
58
58
  }, {});
59
59
  }
60
- async setup({ components, onUploadRequired, accountId, setActiveApp, exit, }) {
60
+ async setup({ components, onUploadRequired, accountId, setActiveApp, exit, port, }) {
61
61
  this.componentsByType = this.arrangeComponentsByType(components);
62
62
  let env;
63
63
  const accountConfig = getConfigAccountById(accountId);
@@ -65,7 +65,7 @@ class DevServerManager_DEPRECATED {
65
65
  env = accountConfig.env;
66
66
  }
67
67
  try {
68
- await startPortManagerServer();
68
+ await startPortManagerServer(port);
69
69
  }
70
70
  catch (e) {
71
71
  logError(e);
@@ -16,6 +16,7 @@ type LocalDevManagerConstructorOptions = {
16
16
  runnableComponents: Component[];
17
17
  env: Environment;
18
18
  exit: ExitFunction;
19
+ port?: number;
19
20
  };
20
21
  declare class LocalDevManager_DEPRECATED {
21
22
  targetAccountId: number;
@@ -39,6 +40,7 @@ declare class LocalDevManager_DEPRECATED {
39
40
  mostRecentUploadWarning: string | null;
40
41
  private devSessionManager;
41
42
  private exit;
43
+ private port?;
42
44
  constructor(options: LocalDevManagerConstructorOptions);
43
45
  setActiveApp(appUid?: string): Promise<void>;
44
46
  setActivePublicAppData(): Promise<void>;
@@ -48,6 +48,7 @@ class LocalDevManager_DEPRECATED {
48
48
  mostRecentUploadWarning;
49
49
  devSessionManager;
50
50
  exit;
51
+ port;
51
52
  constructor(options) {
52
53
  this.targetAccountId = options.targetAccountId;
53
54
  // The account that the project exists in. This is not always the targetAccountId
@@ -67,6 +68,7 @@ class LocalDevManager_DEPRECATED {
67
68
  this.publicAppActiveInstalls = null;
68
69
  this.mostRecentUploadWarning = null;
69
70
  this.exit = options.exit;
71
+ this.port = options.port;
70
72
  this.projectSourceDir = path.join(this.projectDir, this.projectConfig.srcDir);
71
73
  if (!this.targetAccountId || !this.projectConfig || !this.projectDir) {
72
74
  uiLogger.error(lib.LocalDevManager.failedToInitialize);
@@ -337,6 +339,7 @@ class LocalDevManager_DEPRECATED {
337
339
  accountId: this.targetAccountId,
338
340
  setActiveApp: this.setActiveApp.bind(this),
339
341
  exit: this.exit,
342
+ port: this.port,
340
343
  });
341
344
  return true;
342
345
  }
@@ -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,58 @@
1
+ import { triggerAutoRelease, getAutoReleaseStatus, } from '@hubspot/local-dev-lib/api/projects';
2
+ import { DEFAULT_POLLING_DELAY, PREVIEW_POLL_TIMEOUT } from '../constants.js';
3
+ import SpinniesManager from '../ui/SpinniesManager.js';
4
+ import { logError, ApiErrorContext } from '../errorHandlers/index.js';
5
+ import { lib } from '../../lang/en.js';
6
+ export async function triggerAndPollPreview(accountId, projectId, buildId, targetPortalId) {
7
+ let triggerResponse;
8
+ SpinniesManager.add('preview', {
9
+ text: lib.projectPreview.triggeringPreview(buildId, targetPortalId),
10
+ succeedColor: 'white',
11
+ });
12
+ try {
13
+ const { data } = await triggerAutoRelease(accountId, projectId, buildId, targetPortalId);
14
+ triggerResponse = data;
15
+ }
16
+ catch (e) {
17
+ SpinniesManager.fail('preview', {
18
+ text: lib.projectPreview.triggerFailed,
19
+ });
20
+ logError(e, new ApiErrorContext({
21
+ accountId,
22
+ request: 'preview trigger',
23
+ }));
24
+ return { succeeded: false };
25
+ }
26
+ const { releaseTag, appId } = triggerResponse;
27
+ SpinniesManager.update('preview', {
28
+ text: lib.projectPreview.pollingStatus(releaseTag, targetPortalId),
29
+ });
30
+ try {
31
+ await pollPreviewStatus(accountId, projectId, targetPortalId, releaseTag, appId);
32
+ }
33
+ catch (e) {
34
+ SpinniesManager.fail('preview', {
35
+ text: lib.projectPreview.pollFailed,
36
+ });
37
+ logError(e, new ApiErrorContext({
38
+ accountId,
39
+ request: 'preview status',
40
+ }));
41
+ return { succeeded: false, releaseTag, appId };
42
+ }
43
+ SpinniesManager.succeed('preview', {
44
+ text: lib.projectPreview.succeeded(releaseTag, targetPortalId),
45
+ });
46
+ return { succeeded: true, releaseTag, appId };
47
+ }
48
+ async function pollPreviewStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId) {
49
+ const startTime = Date.now();
50
+ while (Date.now() - startTime < PREVIEW_POLL_TIMEOUT) {
51
+ await new Promise(resolve => setTimeout(resolve, DEFAULT_POLLING_DELAY));
52
+ const { data } = await getAutoReleaseStatus(accountId, projectId, targetPortalId, expectedReleaseTag, appId);
53
+ if (data.status === 'COMPLETE') {
54
+ return;
55
+ }
56
+ }
57
+ throw new Error(lib.projectPreview.timeout);
58
+ }
@@ -1,10 +1,19 @@
1
1
  export declare const REQUIRED_PACKAGES_AND_MIN_VERSIONS: {
2
2
  readonly eslint: "9.0.0";
3
- readonly '@typescript-eslint/eslint-plugin': "8.46.4";
4
- readonly '@typescript-eslint/parser': "8.46.4";
3
+ readonly '@eslint/js': "9.0.0";
5
4
  readonly 'typescript-eslint': "8.46.4";
5
+ readonly '@hubspot/eslint-config-ui-extensions': "1.0.0";
6
+ readonly 'eslint-config-prettier': "10.0.0";
7
+ readonly 'eslint-plugin-react': "7.0.0";
8
+ readonly 'eslint-plugin-react-hooks': "7.0.0";
9
+ readonly 'eslint-plugin-unused-imports': "4.0.0";
10
+ readonly prettier: "3.0.0";
6
11
  readonly jiti: "2.6.1";
7
12
  };
13
+ export declare const LINT_SCRIPTS: {
14
+ readonly lint: "eslint .";
15
+ readonly 'lint:fix': "eslint . --fix";
16
+ };
8
17
  export declare function isEslintInstalled(directory: string): boolean;
9
18
  export declare function areAllLintPackagesInstalled(directory: string): boolean;
10
19
  export declare function getMissingLintPackages(directory: string): {
@@ -13,7 +22,7 @@ export declare function getMissingLintPackages(directory: string): {
13
22
  export declare function hasEslintConfig(directory: string): boolean;
14
23
  export declare function hasDeprecatedEslintConfig(directory: string): boolean;
15
24
  export declare function getDeprecatedEslintConfigFiles(directory: string): string[];
16
- export declare function createEslintConfig(directory: string): string;
25
+ export declare function createEslintConfig(directory: string, platformVersion?: string | null): Promise<string>;
17
26
  export declare function lintPackagesInDirectory(directory: string, projectDir?: string): Promise<{
18
27
  success: boolean;
19
28
  output: string;
@@ -31,3 +40,8 @@ export declare function displayLintResults(results: Array<{
31
40
  success: boolean;
32
41
  output: string;
33
42
  }>): void;
43
+ export declare function getMissingLintScripts(directory: string): string[];
44
+ export declare function addLintScriptsToPackageJson(directory: string): {
45
+ added: string[];
46
+ relativePath: string;
47
+ };