@hubspot/cli 8.2.0 → 8.3.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.
- package/api/migrate.js +5 -1
- package/commands/account/auth.js +15 -5
- package/commands/account/use.js +14 -4
- package/commands/app/__tests__/migrate.test.js +2 -2
- package/commands/app/migrate.js +3 -3
- package/commands/auth.js +10 -6
- package/commands/cms/__tests__/upload.test.js +4 -0
- package/commands/getStarted.js +1 -1
- package/commands/hubdb/clear.js +4 -0
- package/commands/hubdb/delete.js +4 -0
- package/commands/hubdb/fetch.js +4 -0
- package/commands/init.js +4 -0
- package/commands/project/__tests__/create.test.js +2 -2
- package/commands/project/__tests__/migrate.test.js +3 -3
- package/commands/project/create.js +3 -3
- package/commands/project/dev/index.js +29 -19
- package/commands/project/download.js +5 -1
- package/commands/project/migrate.js +7 -7
- package/commands/sandbox/__tests__/create.test.js +1 -48
- package/commands/sandbox/create.js +3 -30
- package/commands/testAccount/create.js +4 -0
- package/lang/en.d.ts +3 -3
- package/lang/en.js +4 -4
- package/lib/__tests__/buildAccount.test.js +1 -52
- package/lib/__tests__/sandboxes.test.js +1 -29
- package/lib/accountAuth.js +4 -0
- package/lib/app/__tests__/migrate.test.js +1 -1
- package/lib/app/migrate.js +11 -6
- package/lib/buildAccount.d.ts +1 -6
- package/lib/buildAccount.js +9 -42
- package/lib/constants.d.ts +0 -2
- package/lib/constants.js +0 -2
- package/lib/errors/PromptExitError.d.ts +4 -0
- package/lib/errors/PromptExitError.js +8 -0
- package/lib/getStartedV2Actions.js +1 -1
- package/lib/projects/__tests__/components.test.js +14 -0
- package/lib/projects/components.js +12 -2
- package/lib/projects/localDev/AppDevModeInterface.js +4 -0
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +4 -0
- package/lib/projects/localDev/helpers/account.js +5 -11
- package/lib/prompts/downloadProjectPrompt.js +11 -10
- package/lib/prompts/installAppPrompt.js +3 -2
- package/lib/prompts/personalAccessKeyPrompt.js +3 -2
- package/lib/prompts/projectDevTargetAccountPrompt.js +13 -16
- package/lib/prompts/selectHubDBTablePrompt.js +8 -4
- package/lib/prompts/selectPublicAppForMigrationPrompt.js +12 -6
- package/lib/sandboxes.d.ts +1 -9
- package/lib/sandboxes.js +0 -21
- package/lib/theme/__tests__/migrate.test.js +7 -16
- package/lib/theme/migrate.d.ts +1 -1
- package/lib/theme/migrate.js +1 -5
- package/lib/ui/SpinniesManager.js +2 -0
- package/mcp-server/tools/project/AddFeatureToProjectTool.d.ts +1 -0
- package/mcp-server/tools/project/CreateProjectTool.d.ts +1 -0
- package/mcp-server/tools/project/CreateProjectTool.js +2 -1
- package/mcp-server/tools/project/__tests__/CreateProjectTool.test.js +0 -1
- package/mcp-server/tools/project/constants.d.ts +1 -0
- package/mcp-server/tools/project/constants.js +2 -1
- package/package.json +2 -2
- package/lib/__tests__/sandboxSync.test.d.ts +0 -1
- package/lib/__tests__/sandboxSync.test.js +0 -147
- package/lib/sandboxSync.d.ts +0 -4
- package/lib/sandboxSync.js +0 -102
package/lang/en.d.ts
CHANGED
|
@@ -1494,7 +1494,6 @@ export declare const commands: {
|
|
|
1494
1494
|
};
|
|
1495
1495
|
};
|
|
1496
1496
|
header: string;
|
|
1497
|
-
deprecationWarning: (platformVersion: string) => string;
|
|
1498
1497
|
migrationStatus: {
|
|
1499
1498
|
inProgress: () => string;
|
|
1500
1499
|
success: () => string;
|
|
@@ -3474,6 +3473,7 @@ export declare const lib: {
|
|
|
3474
3473
|
invalidOauthClientSecretCopy: string;
|
|
3475
3474
|
invalidPersonalAccessKey: string;
|
|
3476
3475
|
invalidPersonalAccessKeyCopy: string;
|
|
3476
|
+
authCancelled: string;
|
|
3477
3477
|
};
|
|
3478
3478
|
};
|
|
3479
3479
|
createTemplatePrompt: {
|
|
@@ -3919,8 +3919,8 @@ export declare const lib: {
|
|
|
3919
3919
|
componentsToBeMigrated: (components: string) => string;
|
|
3920
3920
|
componentsThatWillNotBeMigrated: (components: string) => string;
|
|
3921
3921
|
sourceContentsMoved: (newLocation: string) => string;
|
|
3922
|
-
projectMigrationWarningTitle: string;
|
|
3923
|
-
projectMigrationWarning: string;
|
|
3922
|
+
projectMigrationWarningTitle: (platformVersion: string) => string;
|
|
3923
|
+
projectMigrationWarning: (platformVersion: string) => string;
|
|
3924
3924
|
exitWithoutMigrating: string;
|
|
3925
3925
|
success: {
|
|
3926
3926
|
downloadedProject: (projectName: string, projectDest: string) => string;
|
package/lang/en.js
CHANGED
|
@@ -1510,7 +1510,6 @@ export const commands = {
|
|
|
1510
1510
|
},
|
|
1511
1511
|
},
|
|
1512
1512
|
header: `This command will migrate an app to the projects framework. It will walk you through the fields required to complete the migration and download the project source code into a directory of your choosing.\n${uiLink('Learn more about migrating apps to the projects framework', 'https://developers.hubspot.com/docs/platform/migrate-a-public-app-to-projects')}`,
|
|
1513
|
-
deprecationWarning: (platformVersion) => `The ${uiCommandReference('hs project migrate-app')} command is deprecated and will be removed. Use ${uiCommandReference(`hs app migrate --platform-version=${platformVersion}`)} going forward.`,
|
|
1514
1513
|
migrationStatus: {
|
|
1515
1514
|
inProgress: () => `Converting app configuration to ${chalk.bold(LEGACY_PUBLIC_APP_FILE)} component definition ...`,
|
|
1516
1515
|
success: () => `${chalk.bold('Your app was converted and build #1 is deployed')}`,
|
|
@@ -3497,6 +3496,7 @@ export const lib = {
|
|
|
3497
3496
|
invalidOauthClientSecretCopy: 'Please copy the actual OAuth2 client secret rather than the asterisks that mask it.',
|
|
3498
3497
|
invalidPersonalAccessKey: 'You did not enter a valid access key. Please try again.',
|
|
3499
3498
|
invalidPersonalAccessKeyCopy: 'Please copy the actual access key rather than the bullets that mask it.',
|
|
3499
|
+
authCancelled: 'Authentication cancelled.',
|
|
3500
3500
|
},
|
|
3501
3501
|
},
|
|
3502
3502
|
createTemplatePrompt: {
|
|
@@ -3939,11 +3939,11 @@ export const lib = {
|
|
|
3939
3939
|
fileInvalidJson: (path) => `The file "${path}" contains invalid JSON`,
|
|
3940
3940
|
},
|
|
3941
3941
|
migrate: {
|
|
3942
|
-
componentsToBeMigrated: (components) =>
|
|
3942
|
+
componentsToBeMigrated: (components) => `${chalk.bold('The following features will be migrated:')} ${components}`,
|
|
3943
3943
|
componentsThatWillNotBeMigrated: (components) => `[NOTE] These features are not yet supported for migration but will be available later: ${components}`,
|
|
3944
3944
|
sourceContentsMoved: (newLocation) => `The contents of your old source directory have been moved to ${newLocation}, move any required files to the new source directory.`,
|
|
3945
|
-
projectMigrationWarningTitle:
|
|
3946
|
-
projectMigrationWarning: uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion
|
|
3945
|
+
projectMigrationWarningTitle: (platformVersion) => `Important: Migrating to platformVersion ${platformVersion} is irreversible`,
|
|
3946
|
+
projectMigrationWarning: (platformVersion) => uiBetaTag(`Running the ${uiCommandReference('hs project migrate')} command will permanently upgrade your project to platformVersion ${platformVersion}. This action cannot be undone. To ensure you have access to your original files, they will be copied to a new directory (archive) for safekeeping.\n\nThis command will guide you through the process, prompting you to enter the required fields and will download the new project source code into your project source directory.`, false),
|
|
3947
3947
|
exitWithoutMigrating: 'Exiting without migrating',
|
|
3948
3948
|
success: {
|
|
3949
3949
|
downloadedProject: (projectName, projectDest) => `Saved ${projectName} to ${projectDest}`,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev-lib/personalAccessKey';
|
|
2
2
|
import { getConfigAccountIfExists, updateConfigAccount, } from '@hubspot/local-dev-lib/config';
|
|
3
3
|
import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
|
|
4
|
-
import {
|
|
4
|
+
import { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
5
5
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
6
6
|
import { personalAccessKeyPrompt } from '../prompts/personalAccessKeyPrompt.js';
|
|
7
7
|
import { cliAccountNamePrompt } from '../prompts/accountNamePrompt.js';
|
|
@@ -32,7 +32,6 @@ const mockedCliAccountNamePrompt = cliAccountNamePrompt;
|
|
|
32
32
|
const mockedCreateDeveloperTestAccount = createDeveloperTestAccount;
|
|
33
33
|
const mockedFetchDeveloperTestAccountGateSyncStatus = fetchDeveloperTestAccountGateSyncStatus;
|
|
34
34
|
const mockedGenerateDeveloperTestAccountPersonalAccessKey = generateDeveloperTestAccountPersonalAccessKey;
|
|
35
|
-
const mockedCreateSandbox = createSandbox;
|
|
36
35
|
const mockedCreateV2Sandbox = createV2Sandbox;
|
|
37
36
|
const mockedGetPersonalAccessKey = getSandboxPersonalAccessKey;
|
|
38
37
|
const mockedPoll = poll;
|
|
@@ -162,56 +161,6 @@ describe('lib/buildAccount', () => {
|
|
|
162
161
|
await expect(buildAccount.buildDeveloperTestAccount(mockDeveloperTestAccount.accountName, mockParentAccountConfig, mockParentAccountConfig.env, 10)).rejects.toThrow();
|
|
163
162
|
});
|
|
164
163
|
});
|
|
165
|
-
describe('buildSandbox()', () => {
|
|
166
|
-
const mockParentAccountConfig = {
|
|
167
|
-
name: 'Prod account',
|
|
168
|
-
accountId: 123456,
|
|
169
|
-
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD,
|
|
170
|
-
env: 'prod',
|
|
171
|
-
};
|
|
172
|
-
const mockSandbox = {
|
|
173
|
-
sandboxHubId: 56789,
|
|
174
|
-
parentHubId: 123456,
|
|
175
|
-
createdAt: '2025-01-01',
|
|
176
|
-
type: 'STANDARD',
|
|
177
|
-
version: 'V1',
|
|
178
|
-
archived: false,
|
|
179
|
-
name: 'Test Sandbox',
|
|
180
|
-
domain: 'test-sandbox.hubspot.com',
|
|
181
|
-
createdByUser: {
|
|
182
|
-
id: 123456,
|
|
183
|
-
email: 'test@test.com',
|
|
184
|
-
firstName: 'Test',
|
|
185
|
-
lastName: 'User',
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
beforeEach(() => {
|
|
189
|
-
vi.spyOn(buildAccount, 'saveAccountToConfig').mockResolvedValue(mockParentAccountConfig.name);
|
|
190
|
-
mockedCreateSandbox.mockResolvedValue({
|
|
191
|
-
data: { sandbox: mockSandbox, personalAccessKey: 'test-key' },
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
it('should create a standard sandbox successfully', async () => {
|
|
195
|
-
const result = await buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX, mockParentAccountConfig.env, false);
|
|
196
|
-
expect(result).toEqual({
|
|
197
|
-
name: mockSandbox.name,
|
|
198
|
-
personalAccessKey: 'test-key',
|
|
199
|
-
sandbox: mockSandbox,
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
it('should create a development sandbox successfully', async () => {
|
|
203
|
-
const result = await buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, mockParentAccountConfig.env);
|
|
204
|
-
expect(result).toEqual({
|
|
205
|
-
name: mockSandbox.name,
|
|
206
|
-
personalAccessKey: 'test-key',
|
|
207
|
-
sandbox: mockSandbox,
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
it('should handle API errors when creating sandbox', async () => {
|
|
211
|
-
mockedCreateSandbox.mockRejectedValue(new Error('test-error'));
|
|
212
|
-
await expect(buildAccount.buildSandbox(mockSandbox.name, mockParentAccountConfig, HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX, mockParentAccountConfig.env, false)).rejects.toThrow();
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
164
|
describe('buildV2Sandbox()', () => {
|
|
216
165
|
const mockParentAccountConfig = {
|
|
217
166
|
name: 'Prod account',
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { uiLogger } from '../ui/logger.js';
|
|
2
2
|
import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
3
|
-
import { fetchTypes } from '@hubspot/local-dev-lib/api/sandboxSync';
|
|
4
3
|
import { getAllConfigAccounts, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
5
4
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
6
5
|
import { mockHubSpotHttpError } from '../testUtils.js';
|
|
7
|
-
import { getSandboxTypeAsString, getHasSandboxesByType,
|
|
6
|
+
import { getSandboxTypeAsString, getHasSandboxesByType, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
|
|
8
7
|
import { isMissingScopeError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
|
|
9
8
|
vi.mock('@hubspot/local-dev-lib/api/sandboxHubs');
|
|
10
9
|
vi.mock('@hubspot/local-dev-lib/api/sandboxSync');
|
|
@@ -12,7 +11,6 @@ vi.mock('@hubspot/local-dev-lib/config');
|
|
|
12
11
|
vi.mock('@hubspot/local-dev-lib/errors/index');
|
|
13
12
|
const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
|
|
14
13
|
const mockedGetSandboxUsageLimits = getSandboxUsageLimits;
|
|
15
|
-
const mockedFetchTypes = fetchTypes;
|
|
16
14
|
const mockedGetAllConfigAccounts = getAllConfigAccounts;
|
|
17
15
|
const mockedUiLogger = uiLogger;
|
|
18
16
|
const mockedIsMissingScopeError = isMissingScopeError;
|
|
@@ -53,32 +51,6 @@ describe('lib/sandboxes', () => {
|
|
|
53
51
|
expect(getHasSandboxesByType(mockParentAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX)).toBe(false);
|
|
54
52
|
});
|
|
55
53
|
});
|
|
56
|
-
describe('getAvailableSyncTypes()', () => {
|
|
57
|
-
const mockParentAccount = {
|
|
58
|
-
name: 'Parent Account',
|
|
59
|
-
accountId: 123,
|
|
60
|
-
env: 'qa',
|
|
61
|
-
};
|
|
62
|
-
const mockChildAccount = {
|
|
63
|
-
...mockParentAccount,
|
|
64
|
-
accountId: 456,
|
|
65
|
-
};
|
|
66
|
-
it('returns available sync types when fetch is successful', async () => {
|
|
67
|
-
const mockSyncTypes = [{ name: 'type1' }, { name: 'type2' }];
|
|
68
|
-
mockedGetConfigAccountIfExists
|
|
69
|
-
.mockReturnValue(mockParentAccount.accountId)
|
|
70
|
-
.mockReturnValue(mockChildAccount.accountId);
|
|
71
|
-
mockedFetchTypes.mockResolvedValue({
|
|
72
|
-
data: { results: mockSyncTypes },
|
|
73
|
-
});
|
|
74
|
-
const result = await getAvailableSyncTypes(mockParentAccount, mockChildAccount);
|
|
75
|
-
expect(result).toEqual([{ type: 'type1' }, { type: 'type2' }]);
|
|
76
|
-
});
|
|
77
|
-
it('throws error when sync types fetch fails', async () => {
|
|
78
|
-
mockedFetchTypes.mockResolvedValue({ data: { results: null } });
|
|
79
|
-
await expect(getAvailableSyncTypes(mockParentAccount, mockChildAccount)).rejects.toThrow(/Unable to fetch available sandbox sync types/);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
54
|
describe('validateSandboxUsageLimits()', () => {
|
|
83
55
|
const mockAccount = {
|
|
84
56
|
name: 'Test Account',
|
package/lib/accountAuth.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getAccessToken, updateConfigWithAccessToken, } from '@hubspot/local-dev
|
|
|
3
3
|
import { toKebabCase } from '@hubspot/local-dev-lib/text';
|
|
4
4
|
import { handleMerge, handleMigration } from './configMigrate.js';
|
|
5
5
|
import { debugError, logError } from './errorHandlers/index.js';
|
|
6
|
+
import { PromptExitError } from './errors/PromptExitError.js';
|
|
6
7
|
import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
|
|
7
8
|
import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
|
|
8
9
|
import { setAsDefaultAccountPrompt } from './prompts/setAsDefaultAccountPrompt.js';
|
|
@@ -36,6 +37,9 @@ async function updateConfigWithNewAccount(env, configAlreadyExists, providedPers
|
|
|
36
37
|
return updatedConfig;
|
|
37
38
|
}
|
|
38
39
|
catch (e) {
|
|
40
|
+
if (e instanceof PromptExitError) {
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
39
43
|
debugError(e);
|
|
40
44
|
return null;
|
|
41
45
|
}
|
|
@@ -424,7 +424,7 @@ describe('lib/app/migrate', () => {
|
|
|
424
424
|
};
|
|
425
425
|
mockedPoll.mockResolvedValue(mockStatus);
|
|
426
426
|
const result = await pollMigrationStatus(ACCOUNT_ID, MIGRATION_ID);
|
|
427
|
-
expect(mockedPoll).toHaveBeenCalledWith(expect.any(Function), expect.any(Object));
|
|
427
|
+
expect(mockedPoll).toHaveBeenCalledWith(expect.any(Function), expect.any(Object), 300_000);
|
|
428
428
|
expect(result).toBe(mockStatus);
|
|
429
429
|
});
|
|
430
430
|
});
|
package/lib/app/migrate.js
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import { validateUid } from '@hubspot/project-parsing-lib/uid';
|
|
6
6
|
import { UNMIGRATABLE_REASONS } from '@hubspot/local-dev-lib/constants/projects';
|
|
7
7
|
import { mapToUserFacingType } from '@hubspot/project-parsing-lib/transform';
|
|
8
|
+
import { AUTO_GENERATED_COMPONENT_TYPES } from '@hubspot/project-parsing-lib/constants';
|
|
8
9
|
import { MIGRATION_STATUS } from '@hubspot/local-dev-lib/types/Migration';
|
|
9
10
|
import { downloadProject, fetchProjectComponentsMetadata, } from '@hubspot/local-dev-lib/api/projects';
|
|
10
11
|
import { Separator } from '@inquirer/prompts';
|
|
@@ -124,11 +125,15 @@ export async function selectAppToMigrate(allApps, derivedAccountId, appId) {
|
|
|
124
125
|
const migratableComponents = new Set();
|
|
125
126
|
const unmigratableComponents = new Set();
|
|
126
127
|
selectedApp?.migrationComponents.forEach(component => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
const userFacingComponentType = mapToUserFacingType(component.componentType);
|
|
129
|
+
const shouldDisplayComponent = !AUTO_GENERATED_COMPONENT_TYPES.includes(userFacingComponentType);
|
|
130
|
+
if (shouldDisplayComponent) {
|
|
131
|
+
if (component.isSupported) {
|
|
132
|
+
migratableComponents.add(userFacingComponentType);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
unmigratableComponents.add(userFacingComponentType);
|
|
136
|
+
}
|
|
132
137
|
}
|
|
133
138
|
});
|
|
134
139
|
if (migratableComponents.size !== 0) {
|
|
@@ -255,7 +260,7 @@ export async function pollMigrationStatus(derivedAccountId, migrationId, success
|
|
|
255
260
|
return poll(() => checkMigrationStatusV2(derivedAccountId, migrationId), {
|
|
256
261
|
successStates: [...successStates],
|
|
257
262
|
errorStates: [...DEFAULT_POLLING_STATUS_LOOKUP.errorStates],
|
|
258
|
-
});
|
|
263
|
+
}, 300_000);
|
|
259
264
|
}
|
|
260
265
|
export async function finalizeAppMigration(derivedAccountId, migrationId, uidMap, projectName) {
|
|
261
266
|
let pollResponse;
|
package/lib/buildAccount.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DeveloperTestAccountConfig } from '@hubspot/local-dev-lib/types/developerTestAccounts';
|
|
2
2
|
import { Environment } from '@hubspot/local-dev-lib/types/Accounts';
|
|
3
3
|
import { HubSpotConfigAccount } from '@hubspot/local-dev-lib/types/Accounts';
|
|
4
|
-
import {
|
|
4
|
+
import { V2Sandbox } from '@hubspot/local-dev-lib/types/Sandbox';
|
|
5
5
|
import { SandboxAccountType } from '../types/Sandboxes.js';
|
|
6
6
|
export declare function saveAccountToConfig(accountId: number | undefined, accountName: string, env: Environment, personalAccessKey?: string, force?: boolean): Promise<string>;
|
|
7
7
|
export declare function createDeveloperTestAccountV2(parentAccountId: number, testAccountConfig: DeveloperTestAccountConfig): Promise<{
|
|
@@ -10,11 +10,6 @@ export declare function createDeveloperTestAccountV2(parentAccountId: number, te
|
|
|
10
10
|
personalAccessKey?: string;
|
|
11
11
|
}>;
|
|
12
12
|
export declare function buildDeveloperTestAccount(testAccountName: string, parentAccountConfig: HubSpotConfigAccount, env: Environment, portalLimit: number, useV2?: boolean): Promise<number>;
|
|
13
|
-
type SandboxAccount = SandboxResponse & {
|
|
14
|
-
name: string;
|
|
15
|
-
};
|
|
16
|
-
export declare function buildSandbox(sandboxName: string, parentAccountConfig: HubSpotConfigAccount, sandboxType: SandboxAccountType, env: Environment, force?: boolean): Promise<SandboxAccount>;
|
|
17
13
|
export declare function buildV2Sandbox(sandboxName: string, parentAccountConfig: HubSpotConfigAccount, sandboxType: SandboxAccountType, syncObjectRecords: boolean, env: Environment, force?: boolean): Promise<{
|
|
18
14
|
sandbox: V2Sandbox;
|
|
19
15
|
}>;
|
|
20
|
-
export {};
|
package/lib/buildAccount.js
CHANGED
|
@@ -3,13 +3,14 @@ import { getConfigAccountIfExists, updateConfigAccount, } from '@hubspot/local-d
|
|
|
3
3
|
import { uiLogger } from './ui/logger.js';
|
|
4
4
|
import { createDeveloperTestAccount, fetchDeveloperTestAccountGateSyncStatus, generateDeveloperTestAccountPersonalAccessKey, } from '@hubspot/local-dev-lib/api/developerTestAccounts';
|
|
5
5
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
6
|
-
import {
|
|
6
|
+
import { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
7
|
+
import { PromptExitError } from './errors/PromptExitError.js';
|
|
7
8
|
import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
|
|
8
9
|
import { createDeveloperTestAccountConfigPrompt } from './prompts/createDeveloperTestAccountConfigPrompt.js';
|
|
9
10
|
import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
|
|
10
11
|
import SpinniesManager from './ui/SpinniesManager.js';
|
|
11
12
|
import { debugError, logError } from './errorHandlers/index.js';
|
|
12
|
-
import {
|
|
13
|
+
import { SANDBOX_TYPE_MAP_V2, handleSandboxCreateError } from './sandboxes.js';
|
|
13
14
|
import { handleDeveloperTestAccountCreateError } from './developerTestAccounts.js';
|
|
14
15
|
import { lib } from '../lang/en.js';
|
|
15
16
|
import { poll } from './polling.js';
|
|
@@ -127,51 +128,14 @@ export async function buildDeveloperTestAccount(testAccountName, parentAccountCo
|
|
|
127
128
|
await saveAccountToConfig(developerTestAccountId, testAccountName, env, developerTestAccountPersonalAccessKey);
|
|
128
129
|
}
|
|
129
130
|
catch (err) {
|
|
131
|
+
if (err instanceof PromptExitError) {
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
130
134
|
logError(err);
|
|
131
135
|
throw err;
|
|
132
136
|
}
|
|
133
137
|
return developerTestAccountId;
|
|
134
138
|
}
|
|
135
|
-
export async function buildSandbox(sandboxName, parentAccountConfig, sandboxType, env, force = false) {
|
|
136
|
-
const sandboxTypeKey = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX
|
|
137
|
-
? 'standard'
|
|
138
|
-
: 'developer';
|
|
139
|
-
const parentAccountId = parentAccountConfig.accountId;
|
|
140
|
-
if (!parentAccountId) {
|
|
141
|
-
throw new Error(lib.sandbox.create[sandboxTypeKey].loading.fail(''));
|
|
142
|
-
}
|
|
143
|
-
SpinniesManager.init({
|
|
144
|
-
succeedColor: 'white',
|
|
145
|
-
});
|
|
146
|
-
uiLogger.log('');
|
|
147
|
-
SpinniesManager.add('buildSandbox', {
|
|
148
|
-
text: lib.sandbox.create[sandboxTypeKey].loading.add(sandboxName),
|
|
149
|
-
});
|
|
150
|
-
let sandbox;
|
|
151
|
-
try {
|
|
152
|
-
const sandboxApiType = SANDBOX_API_TYPE_MAP[sandboxType];
|
|
153
|
-
const { data } = await createSandbox(parentAccountId, sandboxName, sandboxApiType);
|
|
154
|
-
sandbox = { name: sandboxName, ...data };
|
|
155
|
-
SpinniesManager.succeed('buildSandbox', {
|
|
156
|
-
text: lib.sandbox.create[sandboxTypeKey].loading.succeed(sandboxName, sandbox.sandbox.sandboxHubId.toString()),
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
debugError(e);
|
|
161
|
-
SpinniesManager.fail('buildSandbox', {
|
|
162
|
-
text: lib.sandbox.create[sandboxTypeKey].loading.fail(sandboxName),
|
|
163
|
-
});
|
|
164
|
-
handleSandboxCreateError(e, env, sandboxName, parentAccountId);
|
|
165
|
-
}
|
|
166
|
-
try {
|
|
167
|
-
await saveAccountToConfig(sandbox.sandbox.sandboxHubId, sandboxName, env, sandbox.personalAccessKey, force);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
logError(err);
|
|
171
|
-
throw err;
|
|
172
|
-
}
|
|
173
|
-
return sandbox;
|
|
174
|
-
}
|
|
175
139
|
export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxType, syncObjectRecords, env, force = false) {
|
|
176
140
|
const sandboxTypeKey = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX
|
|
177
141
|
? 'standard'
|
|
@@ -210,6 +174,9 @@ export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxTy
|
|
|
210
174
|
await saveAccountToConfig(sandbox.sandboxHubId, sandboxName, env, pak, force);
|
|
211
175
|
}
|
|
212
176
|
catch (err) {
|
|
177
|
+
if (err instanceof PromptExitError) {
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
213
180
|
logError(err);
|
|
214
181
|
throw err;
|
|
215
182
|
}
|
package/lib/constants.d.ts
CHANGED
|
@@ -77,8 +77,6 @@ export declare const APP_AUTH_TYPES: {
|
|
|
77
77
|
};
|
|
78
78
|
export declare const FEATURES: {
|
|
79
79
|
readonly UNIFIED_APPS: "Developers:UnifiedApps:PrivateBeta";
|
|
80
|
-
readonly SANDBOXES_V2: "sandboxes:v2:enabled";
|
|
81
|
-
readonly SANDBOXES_V2_CLI: "sandboxes:v2:cliEnabled";
|
|
82
80
|
readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
|
|
83
81
|
readonly APPS_HOME: "UIE:AppHome";
|
|
84
82
|
readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
|
package/lib/constants.js
CHANGED
|
@@ -69,8 +69,6 @@ export const APP_AUTH_TYPES = {
|
|
|
69
69
|
};
|
|
70
70
|
export const FEATURES = {
|
|
71
71
|
UNIFIED_APPS: 'Developers:UnifiedApps:PrivateBeta',
|
|
72
|
-
SANDBOXES_V2: 'sandboxes:v2:enabled',
|
|
73
|
-
SANDBOXES_V2_CLI: 'sandboxes:v2:cliEnabled',
|
|
74
72
|
APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
|
|
75
73
|
APPS_HOME: 'UIE:AppHome',
|
|
76
74
|
THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
|
|
@@ -37,7 +37,7 @@ export async function createProjectAction({ projectName, projectDest, }) {
|
|
|
37
37
|
}
|
|
38
38
|
try {
|
|
39
39
|
await cloneGithubRepo(HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, projectDestAbsolute, {
|
|
40
|
-
sourceDir: '
|
|
40
|
+
sourceDir: '2026.03/private-app-get-started-template',
|
|
41
41
|
hideLogs: true,
|
|
42
42
|
});
|
|
43
43
|
const projectConfigPath = path.join(projectDestAbsolute, PROJECT_CONFIG_FILE);
|
|
@@ -174,6 +174,20 @@ describe('lib/projects/components', () => {
|
|
|
174
174
|
expect(mockedFs.readFileSync).toHaveBeenCalledWith('/src/path/package.json', 'utf-8');
|
|
175
175
|
consoleSpy.mockRestore();
|
|
176
176
|
});
|
|
177
|
+
it('skips tooling config files on collision instead of renaming them', () => {
|
|
178
|
+
const collision = {
|
|
179
|
+
...mockCollision,
|
|
180
|
+
collisions: [
|
|
181
|
+
'src/app/cards/.prettierrc.json',
|
|
182
|
+
'src/app/cards/component.js',
|
|
183
|
+
'src/app/cards/eslint.config.js',
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
mockedFs.copyFileSync.mockImplementation(() => { });
|
|
187
|
+
handleComponentCollision(collision);
|
|
188
|
+
expect(mockedFs.copyFileSync).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(mockedFs.copyFileSync).toHaveBeenCalledWith('/src/path/src/app/cards/component.js', '/dest/path/src/app/cards/component-2.js');
|
|
190
|
+
});
|
|
177
191
|
it('falls back to timestamp when maxAttempts is exhausted', () => {
|
|
178
192
|
const collision = {
|
|
179
193
|
...mockCollision,
|
|
@@ -114,9 +114,19 @@ export function handleComponentCollision({ dest, src, collisions }) {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
const filenameDifferentiator = generateSafeFilenameDifferentiator(sourceFiles, hsMetaFiles);
|
|
117
|
-
// Exclude markdown files fromthe rename process because they should not be duplicated
|
|
118
117
|
const sourceFilenameMapping = sourceFiles
|
|
119
|
-
.filter(filename =>
|
|
118
|
+
.filter(filename => {
|
|
119
|
+
// Exclude markdown files from the rename process because they should not be duplicated
|
|
120
|
+
if (filename.endsWith('.md')) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
// Also exclude the tooling config we add to new projects
|
|
124
|
+
const base = path.parse(filename).base;
|
|
125
|
+
if (base === '.prettierrc.json' || base === 'eslint.config.js') {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
})
|
|
120
130
|
.reduce((acc, filename) => {
|
|
121
131
|
return {
|
|
122
132
|
...acc,
|
|
@@ -6,6 +6,7 @@ import { EXIT_CODES } from '../../enums/exitCodes.js';
|
|
|
6
6
|
import { isAppIRNode } from '../../projects/structure.js';
|
|
7
7
|
import { uiLine } from '../../ui/index.js';
|
|
8
8
|
import { logError } from '../../errorHandlers/index.js';
|
|
9
|
+
import { PromptExitError } from '../../errors/PromptExitError.js';
|
|
9
10
|
import { installAppAutoPrompt, installAppBrowserPrompt, } from '../../prompts/installAppPrompt.js';
|
|
10
11
|
import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
11
12
|
import { lib } from '../../../lang/en.js';
|
|
@@ -313,6 +314,9 @@ class AppDevModeInterface {
|
|
|
313
314
|
}
|
|
314
315
|
}
|
|
315
316
|
catch (e) {
|
|
317
|
+
if (e instanceof PromptExitError) {
|
|
318
|
+
throw e;
|
|
319
|
+
}
|
|
316
320
|
if (SpinniesManager.pick('fetchAppData')) {
|
|
317
321
|
SpinniesManager.fail('fetchAppData', {
|
|
318
322
|
text: lib.AppDevModeInterface.fetchAppData.error,
|
|
@@ -15,6 +15,7 @@ import { componentIsApp, componentIsPublicApp, CONFIG_FILES, getAppCardConfigs,
|
|
|
15
15
|
import { ComponentTypes, } from '../../../types/Projects.js';
|
|
16
16
|
import { UI_COLORS, uiCommandReference, uiAccountDescription, uiLink, uiLine, } from '../../ui/index.js';
|
|
17
17
|
import { logError } from '../../errorHandlers/index.js';
|
|
18
|
+
import { PromptExitError } from '../../errors/PromptExitError.js';
|
|
18
19
|
import { installAppBrowserPrompt } from '../../prompts/installAppPrompt.js';
|
|
19
20
|
import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
20
21
|
import { handleKeypress } from '../../process.js';
|
|
@@ -89,6 +90,9 @@ class LocalDevManager_DEPRECATED {
|
|
|
89
90
|
await this.checkPublicAppInstallation();
|
|
90
91
|
}
|
|
91
92
|
catch (e) {
|
|
93
|
+
if (e instanceof PromptExitError) {
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
92
96
|
logError(e);
|
|
93
97
|
}
|
|
94
98
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
|
|
2
|
-
import {
|
|
2
|
+
import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
3
3
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
4
4
|
import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls';
|
|
5
5
|
import { isMissingScopeError } from '@hubspot/local-dev-lib/errors/index';
|
|
@@ -18,12 +18,10 @@ import { selectDeveloperTestTargetAccountPrompt } from '../../../prompts/project
|
|
|
18
18
|
import { selectSandboxTargetAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
|
|
19
19
|
import { validateSandboxUsageLimits } from '../../../sandboxes.js';
|
|
20
20
|
import { logError } from '../../../errorHandlers/index.js';
|
|
21
|
-
import { syncSandbox } from '../../../sandboxSync.js';
|
|
22
|
-
import { getAvailableSyncTypes } from '../../../sandboxes.js';
|
|
23
21
|
import { hubspotAccountNamePrompt } from '../../../prompts/accountNamePrompt.js';
|
|
24
22
|
import { trackCommandMetadataUsage } from '../../../usageTracking.js';
|
|
25
23
|
import { validateDevTestAccountUsageLimits } from '../../../developerTestAccounts.js';
|
|
26
|
-
import {
|
|
24
|
+
import { buildDeveloperTestAccount, saveAccountToConfig, buildV2Sandbox, } from '../../../buildAccount.js';
|
|
27
25
|
import { debugError } from '../../../errorHandlers/index.js';
|
|
28
26
|
import { listPrompt } from '../../../prompts/promptUtils.js';
|
|
29
27
|
import { confirmUseExistingDeveloperTestAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
|
|
@@ -119,13 +117,9 @@ export async function createSandboxForLocalDev(accountId, accountConfig, env) {
|
|
|
119
117
|
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
120
118
|
});
|
|
121
119
|
trackCommandMetadataUsage('sandbox-create', { step: 'project-dev' }, accountId);
|
|
122
|
-
const result = await
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const syncTasks = await getAvailableSyncTypes(accountConfig, sandboxAccountConfig);
|
|
126
|
-
// For v1 sandboxes, keep sync here. Once we migrate to v2, this will be handled by BE automatically
|
|
127
|
-
await syncSandbox(sandboxAccountConfig, accountConfig, env, syncTasks, true);
|
|
128
|
-
return targetAccountId;
|
|
120
|
+
const result = await buildV2Sandbox(name, accountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, false, // syncObjectRecords
|
|
121
|
+
env);
|
|
122
|
+
return result.sandbox.sandboxHubId;
|
|
129
123
|
}
|
|
130
124
|
catch (err) {
|
|
131
125
|
logError(err);
|
|
@@ -2,21 +2,22 @@ import { promptUser } from './promptUtils.js';
|
|
|
2
2
|
import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
3
3
|
import { fetchProjects } from '@hubspot/local-dev-lib/api/projects';
|
|
4
4
|
import { logError, ApiErrorContext } from '../errorHandlers/index.js';
|
|
5
|
-
import { uiLogger } from '../ui/logger.js';
|
|
6
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
7
5
|
import { lib } from '../../lang/en.js';
|
|
6
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
7
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
|
+
import { uiLogger } from '../ui/logger.js';
|
|
8
9
|
async function createProjectsList(accountId) {
|
|
9
|
-
|
|
10
|
-
if (accountId) {
|
|
11
|
-
const { data: projects } = await fetchProjects(accountId);
|
|
12
|
-
return projects.results;
|
|
13
|
-
}
|
|
10
|
+
if (!accountId) {
|
|
14
11
|
uiLogger.error(lib.prompts.downloadProjectPrompt.errors.accountIdRequired);
|
|
15
|
-
|
|
12
|
+
throw new PromptExitError(lib.prompts.downloadProjectPrompt.errors.accountIdRequired, EXIT_CODES.ERROR);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const { data: projects } = await fetchProjects(accountId);
|
|
16
|
+
return projects.results;
|
|
16
17
|
}
|
|
17
18
|
catch (e) {
|
|
18
|
-
logError(e,
|
|
19
|
-
|
|
19
|
+
logError(e, new ApiErrorContext({ accountId }));
|
|
20
|
+
throw new PromptExitError('Failed to fetch projects', EXIT_CODES.ERROR);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
export async function downloadProjectPrompt(promptOptions) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import open from 'open';
|
|
2
2
|
import { promptUser } from './promptUtils.js';
|
|
3
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
4
3
|
import { lib } from '../../lang/en.js';
|
|
5
4
|
import { uiLogger } from '../ui/logger.js';
|
|
5
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
6
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
6
7
|
export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
7
8
|
uiLogger.log('');
|
|
8
9
|
if (isReinstall) {
|
|
@@ -20,7 +21,7 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
|
20
21
|
});
|
|
21
22
|
if (!isReinstall && !shouldOpenBrowser) {
|
|
22
23
|
uiLogger.log(lib.prompts.installAppPrompt.decline);
|
|
23
|
-
|
|
24
|
+
throw new PromptExitError(lib.prompts.installAppPrompt.decline, EXIT_CODES.SUCCESS);
|
|
24
25
|
}
|
|
25
26
|
else if (!shouldOpenBrowser) {
|
|
26
27
|
return;
|
|
@@ -6,8 +6,9 @@ import { uiLogger } from '../ui/logger.js';
|
|
|
6
6
|
import { promptUser } from './promptUtils.js';
|
|
7
7
|
import { getCliAccountNamePromptConfig, } from './accountNamePrompt.js';
|
|
8
8
|
import { uiInfoSection } from '../ui/index.js';
|
|
9
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
10
9
|
import { lib } from '../../lang/en.js';
|
|
10
|
+
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
11
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
11
12
|
/**
|
|
12
13
|
* Displays notification to user that we are about to open the browser,
|
|
13
14
|
* then opens their browser to the personal-access-key shortlink
|
|
@@ -27,7 +28,7 @@ export async function personalAccessKeyPrompt({ env, account, }) {
|
|
|
27
28
|
]);
|
|
28
29
|
if (!choice) {
|
|
29
30
|
deleteConfigFileIfEmpty();
|
|
30
|
-
|
|
31
|
+
throw new PromptExitError(lib.prompts.personalAccessKeyPrompt.errors.authCancelled, EXIT_CODES.SUCCESS);
|
|
31
32
|
}
|
|
32
33
|
if (choice ===
|
|
33
34
|
lib.prompts.personalAccessKeyPrompt.personalAccessKeyPromptChoices
|