@hubspot/cli 8.0.10-experimental.3 → 8.0.10-experimental.4
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 +5 -15
- package/commands/account/use.js +4 -14
- package/commands/app/migrate.js +2 -2
- package/commands/auth.js +6 -10
- package/commands/cms/__tests__/upload.test.js +4 -0
- package/commands/hubdb/clear.js +0 -4
- package/commands/hubdb/delete.js +0 -4
- package/commands/hubdb/fetch.js +0 -4
- package/commands/init.js +0 -4
- package/commands/project/__tests__/migrate.test.js +2 -2
- package/commands/project/dev/index.js +19 -29
- package/commands/project/download.js +1 -5
- package/commands/project/migrate.js +6 -6
- package/commands/sandbox/__tests__/create.test.js +48 -1
- package/commands/sandbox/create.js +30 -3
- package/commands/testAccount/create.js +0 -4
- package/lang/en.d.ts +2 -3
- package/lang/en.js +2 -3
- package/lib/__tests__/buildAccount.test.js +52 -1
- package/lib/__tests__/sandboxSync.test.d.ts +1 -0
- package/lib/__tests__/sandboxSync.test.js +147 -0
- package/lib/__tests__/sandboxes.test.js +29 -1
- package/lib/accountAuth.js +0 -4
- package/lib/buildAccount.d.ts +6 -1
- package/lib/buildAccount.js +42 -9
- package/lib/constants.d.ts +2 -0
- package/lib/constants.js +2 -0
- package/lib/projects/__tests__/components.test.js +0 -14
- package/lib/projects/components.js +2 -12
- package/lib/projects/localDev/AppDevModeInterface.js +0 -4
- package/lib/projects/localDev/LocalDevManager_DEPRECATED.js +0 -4
- package/lib/projects/localDev/helpers/account.js +11 -5
- package/lib/prompts/downloadProjectPrompt.js +10 -11
- package/lib/prompts/installAppPrompt.js +2 -3
- package/lib/prompts/personalAccessKeyPrompt.js +2 -3
- package/lib/prompts/projectDevTargetAccountPrompt.js +16 -13
- package/lib/prompts/selectHubDBTablePrompt.js +4 -8
- package/lib/prompts/selectPublicAppForMigrationPrompt.js +6 -12
- package/lib/sandboxSync.d.ts +4 -0
- package/lib/sandboxSync.js +102 -0
- package/lib/sandboxes.d.ts +9 -1
- package/lib/sandboxes.js +21 -0
- package/lib/theme/__tests__/migrate.test.js +6 -11
- package/lib/theme/migrate.d.ts +1 -1
- package/lib/theme/migrate.js +1 -5
- package/package.json +3 -3
- package/lib/errors/PromptExitError.d.ts +0 -4
- package/lib/errors/PromptExitError.js +0 -8
|
@@ -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 { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
4
|
+
import { createSandbox, 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,6 +32,7 @@ const mockedCliAccountNamePrompt = cliAccountNamePrompt;
|
|
|
32
32
|
const mockedCreateDeveloperTestAccount = createDeveloperTestAccount;
|
|
33
33
|
const mockedFetchDeveloperTestAccountGateSyncStatus = fetchDeveloperTestAccountGateSyncStatus;
|
|
34
34
|
const mockedGenerateDeveloperTestAccountPersonalAccessKey = generateDeveloperTestAccountPersonalAccessKey;
|
|
35
|
+
const mockedCreateSandbox = createSandbox;
|
|
35
36
|
const mockedCreateV2Sandbox = createV2Sandbox;
|
|
36
37
|
const mockedGetPersonalAccessKey = getSandboxPersonalAccessKey;
|
|
37
38
|
const mockedPoll = poll;
|
|
@@ -161,6 +162,56 @@ describe('lib/buildAccount', () => {
|
|
|
161
162
|
await expect(buildAccount.buildDeveloperTestAccount(mockDeveloperTestAccount.accountName, mockParentAccountConfig, mockParentAccountConfig.env, 10)).rejects.toThrow();
|
|
162
163
|
});
|
|
163
164
|
});
|
|
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
|
+
});
|
|
164
215
|
describe('buildV2Sandbox()', () => {
|
|
165
216
|
const mockParentAccountConfig = {
|
|
166
217
|
name: 'Prod account',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { uiLogger } from '../ui/logger.js';
|
|
2
|
+
import { initiateSync } from '@hubspot/local-dev-lib/api/sandboxSync';
|
|
3
|
+
import { getConfigAccountIfExists, getConfigAccountById, } from '@hubspot/local-dev-lib/config';
|
|
4
|
+
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
5
|
+
import { mockHubSpotHttpError } from '../testUtils.js';
|
|
6
|
+
import { getAvailableSyncTypes } from '../sandboxes.js';
|
|
7
|
+
import { syncSandbox } from '../sandboxSync.js';
|
|
8
|
+
import SpinniesManager from '../ui/SpinniesManager.js';
|
|
9
|
+
vi.mock('@hubspot/local-dev-lib/api/sandboxSync');
|
|
10
|
+
vi.mock('@hubspot/local-dev-lib/config');
|
|
11
|
+
vi.mock('../sandboxes');
|
|
12
|
+
vi.mock('../ui/SpinniesManager');
|
|
13
|
+
const mockedUiLogger = uiLogger;
|
|
14
|
+
const mockedInitiateSync = initiateSync;
|
|
15
|
+
const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
|
|
16
|
+
const mockedGetConfigAccountById = getConfigAccountById;
|
|
17
|
+
const mockedGetAvailableSyncTypes = getAvailableSyncTypes;
|
|
18
|
+
const mockedSpinniesInit = SpinniesManager.init;
|
|
19
|
+
const mockedSpinniesAdd = SpinniesManager.add;
|
|
20
|
+
const mockedSpinniesSucceed = SpinniesManager.succeed;
|
|
21
|
+
const mockedSpinniesFail = SpinniesManager.fail;
|
|
22
|
+
describe('lib/sandboxSync', () => {
|
|
23
|
+
const mockEnv = 'qa';
|
|
24
|
+
const mockParentAccount = {
|
|
25
|
+
name: 'Parent Account',
|
|
26
|
+
accountId: 123,
|
|
27
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX,
|
|
28
|
+
env: mockEnv,
|
|
29
|
+
authType: 'personalaccesskey',
|
|
30
|
+
};
|
|
31
|
+
const mockChildAccount = {
|
|
32
|
+
name: 'Child Account',
|
|
33
|
+
accountId: 456,
|
|
34
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
35
|
+
env: mockEnv,
|
|
36
|
+
authType: 'personalaccesskey',
|
|
37
|
+
};
|
|
38
|
+
const mockChildAccountWithMissingId = {
|
|
39
|
+
name: 'Child Account',
|
|
40
|
+
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
41
|
+
env: mockEnv,
|
|
42
|
+
authType: 'personalaccesskey',
|
|
43
|
+
};
|
|
44
|
+
const mockSyncTasks = [{ type: 'mock-sync-type' }];
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockedGetConfigAccountIfExists
|
|
47
|
+
.mockReturnValueOnce(mockChildAccount)
|
|
48
|
+
.mockReturnValueOnce(mockParentAccount);
|
|
49
|
+
mockedGetAvailableSyncTypes.mockResolvedValue(mockSyncTasks);
|
|
50
|
+
// Mock SpinniesManager methods to prevent spinner errors
|
|
51
|
+
mockedSpinniesInit.mockImplementation(() => { });
|
|
52
|
+
mockedSpinniesAdd.mockImplementation(() => { });
|
|
53
|
+
mockedSpinniesSucceed.mockImplementation(() => { });
|
|
54
|
+
mockedSpinniesFail.mockImplementation(() => { });
|
|
55
|
+
// Mock account config for uiAccountDescription calls
|
|
56
|
+
mockedGetConfigAccountById.mockImplementation(accountId => {
|
|
57
|
+
if (accountId === mockChildAccount.accountId) {
|
|
58
|
+
return mockChildAccount;
|
|
59
|
+
}
|
|
60
|
+
if (accountId === mockParentAccount.accountId) {
|
|
61
|
+
return mockParentAccount;
|
|
62
|
+
}
|
|
63
|
+
return undefined; // Don't throw, just return undefined for unknown accounts
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('syncSandbox()', () => {
|
|
67
|
+
it('successfully syncs a sandbox with provided sync tasks', async () => {
|
|
68
|
+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
|
|
69
|
+
await syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks);
|
|
70
|
+
expect(mockedSpinniesInit).toHaveBeenCalled();
|
|
71
|
+
expect(mockedSpinniesAdd).toHaveBeenCalled();
|
|
72
|
+
expect(mockedInitiateSync).toHaveBeenCalledWith(mockParentAccount.accountId, mockChildAccount.accountId, mockSyncTasks, mockChildAccount.accountId);
|
|
73
|
+
expect(mockedSpinniesSucceed).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
it('fetches sync types when no tasks are provided', async () => {
|
|
76
|
+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
|
|
77
|
+
await syncSandbox(mockChildAccount, mockParentAccount, mockEnv, []);
|
|
78
|
+
expect(mockedGetAvailableSyncTypes).toHaveBeenCalledWith(mockParentAccount, mockChildAccount);
|
|
79
|
+
expect(mockedGetAvailableSyncTypes).toHaveBeenCalledWith(mockParentAccount, mockChildAccount);
|
|
80
|
+
expect(mockedInitiateSync).toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
it('throws error when account IDs are missing', async () => {
|
|
83
|
+
const errorRegex = new RegExp(`because your account has been removed from`);
|
|
84
|
+
await expect(syncSandbox(mockChildAccountWithMissingId, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toThrow(errorRegex);
|
|
85
|
+
});
|
|
86
|
+
it('handles sync in progress error', async () => {
|
|
87
|
+
const error = mockHubSpotHttpError('', {
|
|
88
|
+
status: 429,
|
|
89
|
+
data: {
|
|
90
|
+
category: 'RATE_LIMITS',
|
|
91
|
+
subCategory: 'sandboxes-sync-api.SYNC_IN_PROGRESS',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
mockedInitiateSync.mockRejectedValue(error);
|
|
95
|
+
await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toEqual(error);
|
|
96
|
+
expect(mockedSpinniesFail).toHaveBeenCalled();
|
|
97
|
+
expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Couldn't run the sync because there's another sync in progress/));
|
|
98
|
+
});
|
|
99
|
+
it('handles invalid user error', async () => {
|
|
100
|
+
const error = mockHubSpotHttpError('', {
|
|
101
|
+
status: 403,
|
|
102
|
+
data: {
|
|
103
|
+
category: 'BANNED',
|
|
104
|
+
subCategory: 'sandboxes-sync-api.SYNC_NOT_ALLOWED_INVALID_USER',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
mockedInitiateSync.mockRejectedValue(error);
|
|
108
|
+
await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toEqual(error);
|
|
109
|
+
expect(mockedSpinniesFail).toHaveBeenCalled();
|
|
110
|
+
expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/because your account has been removed from/));
|
|
111
|
+
});
|
|
112
|
+
it('handles not super admin error', async () => {
|
|
113
|
+
const error = mockHubSpotHttpError('', {
|
|
114
|
+
status: 403,
|
|
115
|
+
data: {
|
|
116
|
+
category: 'BANNED',
|
|
117
|
+
subCategory: 'sandboxes-sync-api.SYNC_NOT_ALLOWED_INVALID_USERID',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
mockedInitiateSync.mockRejectedValue(error);
|
|
121
|
+
await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toEqual(error);
|
|
122
|
+
expect(mockedSpinniesFail).toHaveBeenCalled();
|
|
123
|
+
expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/Couldn't run the sync because you are not a super admin in/));
|
|
124
|
+
});
|
|
125
|
+
it('handles sandbox not found error', async () => {
|
|
126
|
+
const error = mockHubSpotHttpError('', {
|
|
127
|
+
status: 404,
|
|
128
|
+
data: {
|
|
129
|
+
category: 'OBJECT_NOT_FOUND',
|
|
130
|
+
subCategory: 'SandboxErrors.SANDBOX_NOT_FOUND',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
mockedInitiateSync.mockRejectedValue(error);
|
|
134
|
+
await expect(syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks)).rejects.toEqual(error);
|
|
135
|
+
expect(mockedSpinniesFail).toHaveBeenCalled();
|
|
136
|
+
expect(mockedUiLogger.error).toHaveBeenCalledWith(expect.stringMatching(/may have been deleted through the UI/));
|
|
137
|
+
});
|
|
138
|
+
it('displays slim info message when specified', async () => {
|
|
139
|
+
mockedInitiateSync.mockResolvedValue({ status: 'SUCCESS' });
|
|
140
|
+
await syncSandbox(mockChildAccount, mockParentAccount, mockEnv, mockSyncTasks, true);
|
|
141
|
+
expect(mockedUiLogger.info).not.toHaveBeenCalled();
|
|
142
|
+
expect(mockedSpinniesSucceed).toHaveBeenCalledWith('sandboxSync', expect.objectContaining({
|
|
143
|
+
text: expect.stringMatching(/Initiated sync of object definitions from production to /),
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
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';
|
|
3
4
|
import { getAllConfigAccounts, getConfigAccountIfExists, } from '@hubspot/local-dev-lib/config';
|
|
4
5
|
import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config';
|
|
5
6
|
import { mockHubSpotHttpError } from '../testUtils.js';
|
|
6
|
-
import { getSandboxTypeAsString, getHasSandboxesByType, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
|
|
7
|
+
import { getSandboxTypeAsString, getHasSandboxesByType, getAvailableSyncTypes, validateSandboxUsageLimits, handleSandboxCreateError, } from '../sandboxes.js';
|
|
7
8
|
import { isMissingScopeError, isSpecifiedError, } from '@hubspot/local-dev-lib/errors/index';
|
|
8
9
|
vi.mock('@hubspot/local-dev-lib/api/sandboxHubs');
|
|
9
10
|
vi.mock('@hubspot/local-dev-lib/api/sandboxSync');
|
|
@@ -11,6 +12,7 @@ vi.mock('@hubspot/local-dev-lib/config');
|
|
|
11
12
|
vi.mock('@hubspot/local-dev-lib/errors/index');
|
|
12
13
|
const mockedGetConfigAccountIfExists = getConfigAccountIfExists;
|
|
13
14
|
const mockedGetSandboxUsageLimits = getSandboxUsageLimits;
|
|
15
|
+
const mockedFetchTypes = fetchTypes;
|
|
14
16
|
const mockedGetAllConfigAccounts = getAllConfigAccounts;
|
|
15
17
|
const mockedUiLogger = uiLogger;
|
|
16
18
|
const mockedIsMissingScopeError = isMissingScopeError;
|
|
@@ -51,6 +53,32 @@ describe('lib/sandboxes', () => {
|
|
|
51
53
|
expect(getHasSandboxesByType(mockParentAccount, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX)).toBe(false);
|
|
52
54
|
});
|
|
53
55
|
});
|
|
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
|
+
});
|
|
54
82
|
describe('validateSandboxUsageLimits()', () => {
|
|
55
83
|
const mockAccount = {
|
|
56
84
|
name: 'Test Account',
|
package/lib/accountAuth.js
CHANGED
|
@@ -3,7 +3,6 @@ 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';
|
|
7
6
|
import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
|
|
8
7
|
import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
|
|
9
8
|
import { setAsDefaultAccountPrompt } from './prompts/setAsDefaultAccountPrompt.js';
|
|
@@ -37,9 +36,6 @@ async function updateConfigWithNewAccount(env, configAlreadyExists, providedPers
|
|
|
37
36
|
return updatedConfig;
|
|
38
37
|
}
|
|
39
38
|
catch (e) {
|
|
40
|
-
if (e instanceof PromptExitError) {
|
|
41
|
-
throw e;
|
|
42
|
-
}
|
|
43
39
|
debugError(e);
|
|
44
40
|
return null;
|
|
45
41
|
}
|
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 { V2Sandbox } from '@hubspot/local-dev-lib/types/Sandbox';
|
|
4
|
+
import { SandboxResponse, 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,6 +10,11 @@ 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>;
|
|
13
17
|
export declare function buildV2Sandbox(sandboxName: string, parentAccountConfig: HubSpotConfigAccount, sandboxType: SandboxAccountType, syncObjectRecords: boolean, env: Environment, force?: boolean): Promise<{
|
|
14
18
|
sandbox: V2Sandbox;
|
|
15
19
|
}>;
|
|
20
|
+
export {};
|
package/lib/buildAccount.js
CHANGED
|
@@ -3,14 +3,13 @@ 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 { createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
7
|
-
import { PromptExitError } from './errors/PromptExitError.js';
|
|
6
|
+
import { createSandbox, createV2Sandbox, getSandboxPersonalAccessKey, } from '@hubspot/local-dev-lib/api/sandboxHubs';
|
|
8
7
|
import { personalAccessKeyPrompt } from './prompts/personalAccessKeyPrompt.js';
|
|
9
8
|
import { createDeveloperTestAccountConfigPrompt } from './prompts/createDeveloperTestAccountConfigPrompt.js';
|
|
10
9
|
import { cliAccountNamePrompt } from './prompts/accountNamePrompt.js';
|
|
11
10
|
import SpinniesManager from './ui/SpinniesManager.js';
|
|
12
11
|
import { debugError, logError } from './errorHandlers/index.js';
|
|
13
|
-
import { SANDBOX_TYPE_MAP_V2, handleSandboxCreateError } from './sandboxes.js';
|
|
12
|
+
import { SANDBOX_API_TYPE_MAP, SANDBOX_TYPE_MAP_V2, handleSandboxCreateError, } from './sandboxes.js';
|
|
14
13
|
import { handleDeveloperTestAccountCreateError } from './developerTestAccounts.js';
|
|
15
14
|
import { lib } from '../lang/en.js';
|
|
16
15
|
import { poll } from './polling.js';
|
|
@@ -128,14 +127,51 @@ export async function buildDeveloperTestAccount(testAccountName, parentAccountCo
|
|
|
128
127
|
await saveAccountToConfig(developerTestAccountId, testAccountName, env, developerTestAccountPersonalAccessKey);
|
|
129
128
|
}
|
|
130
129
|
catch (err) {
|
|
131
|
-
if (err instanceof PromptExitError) {
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
130
|
logError(err);
|
|
135
131
|
throw err;
|
|
136
132
|
}
|
|
137
133
|
return developerTestAccountId;
|
|
138
134
|
}
|
|
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
|
+
}
|
|
139
175
|
export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxType, syncObjectRecords, env, force = false) {
|
|
140
176
|
const sandboxTypeKey = sandboxType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX
|
|
141
177
|
? 'standard'
|
|
@@ -174,9 +210,6 @@ export async function buildV2Sandbox(sandboxName, parentAccountConfig, sandboxTy
|
|
|
174
210
|
await saveAccountToConfig(sandbox.sandboxHubId, sandboxName, env, pak, force);
|
|
175
211
|
}
|
|
176
212
|
catch (err) {
|
|
177
|
-
if (err instanceof PromptExitError) {
|
|
178
|
-
throw err;
|
|
179
|
-
}
|
|
180
213
|
logError(err);
|
|
181
214
|
throw err;
|
|
182
215
|
}
|
package/lib/constants.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ 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";
|
|
80
82
|
readonly APP_EVENTS: "Developers:UnifiedApps:AppEventsAccess";
|
|
81
83
|
readonly APPS_HOME: "UIE:AppHome";
|
|
82
84
|
readonly THEME_MIGRATION_2025_2: "Developers:ProjectThemeMigrations:2025.2";
|
package/lib/constants.js
CHANGED
|
@@ -69,6 +69,8 @@ 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',
|
|
72
74
|
APP_EVENTS: 'Developers:UnifiedApps:AppEventsAccess',
|
|
73
75
|
APPS_HOME: 'UIE:AppHome',
|
|
74
76
|
THEME_MIGRATION_2025_2: 'Developers:ProjectThemeMigrations:2025.2',
|
|
@@ -174,20 +174,6 @@ 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
|
-
});
|
|
191
177
|
it('falls back to timestamp when maxAttempts is exhausted', () => {
|
|
192
178
|
const collision = {
|
|
193
179
|
...mockCollision,
|
|
@@ -114,19 +114,9 @@ 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
|
|
117
118
|
const sourceFilenameMapping = sourceFiles
|
|
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
|
-
})
|
|
119
|
+
.filter(filename => !filename.endsWith('.md'))
|
|
130
120
|
.reduce((acc, filename) => {
|
|
131
121
|
return {
|
|
132
122
|
...acc,
|
|
@@ -6,7 +6,6 @@ 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';
|
|
10
9
|
import { installAppAutoPrompt, installAppBrowserPrompt, } from '../../prompts/installAppPrompt.js';
|
|
11
10
|
import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
12
11
|
import { lib } from '../../../lang/en.js';
|
|
@@ -314,9 +313,6 @@ class AppDevModeInterface {
|
|
|
314
313
|
}
|
|
315
314
|
}
|
|
316
315
|
catch (e) {
|
|
317
|
-
if (e instanceof PromptExitError) {
|
|
318
|
-
throw e;
|
|
319
|
-
}
|
|
320
316
|
if (SpinniesManager.pick('fetchAppData')) {
|
|
321
317
|
SpinniesManager.fail('fetchAppData', {
|
|
322
318
|
text: lib.AppDevModeInterface.fetchAppData.error,
|
|
@@ -15,7 +15,6 @@ 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';
|
|
19
18
|
import { installAppBrowserPrompt } from '../../prompts/installAppPrompt.js';
|
|
20
19
|
import { confirmPrompt } from '../../prompts/promptUtils.js';
|
|
21
20
|
import { handleKeypress } from '../../process.js';
|
|
@@ -90,9 +89,6 @@ class LocalDevManager_DEPRECATED {
|
|
|
90
89
|
await this.checkPublicAppInstallation();
|
|
91
90
|
}
|
|
92
91
|
catch (e) {
|
|
93
|
-
if (e instanceof PromptExitError) {
|
|
94
|
-
throw e;
|
|
95
|
-
}
|
|
96
92
|
logError(e);
|
|
97
93
|
}
|
|
98
94
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HUBSPOT_ACCOUNT_TYPE_STRINGS } from '@hubspot/local-dev-lib/constants/config';
|
|
2
|
-
import { getConfigAccountIfExists } from '@hubspot/local-dev-lib/config';
|
|
2
|
+
import { getConfigAccountById, 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,10 +18,12 @@ 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';
|
|
21
23
|
import { hubspotAccountNamePrompt } from '../../../prompts/accountNamePrompt.js';
|
|
22
24
|
import { trackCommandMetadataUsage } from '../../../usageTracking.js';
|
|
23
25
|
import { validateDevTestAccountUsageLimits } from '../../../developerTestAccounts.js';
|
|
24
|
-
import { buildDeveloperTestAccount, saveAccountToConfig,
|
|
26
|
+
import { buildSandbox, buildDeveloperTestAccount, saveAccountToConfig, } from '../../../buildAccount.js';
|
|
25
27
|
import { debugError } from '../../../errorHandlers/index.js';
|
|
26
28
|
import { listPrompt } from '../../../prompts/promptUtils.js';
|
|
27
29
|
import { confirmUseExistingDeveloperTestAccountPrompt } from '../../../prompts/projectDevTargetAccountPrompt.js';
|
|
@@ -117,9 +119,13 @@ export async function createSandboxForLocalDev(accountId, accountConfig, env) {
|
|
|
117
119
|
accountType: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX,
|
|
118
120
|
});
|
|
119
121
|
trackCommandMetadataUsage('sandbox-create', { step: 'project-dev' }, accountId);
|
|
120
|
-
const result = await
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const result = await buildSandbox(name, accountConfig, HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, env);
|
|
123
|
+
const targetAccountId = result.sandbox.sandboxHubId;
|
|
124
|
+
const sandboxAccountConfig = getConfigAccountById(result.sandbox.sandboxHubId);
|
|
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;
|
|
123
129
|
}
|
|
124
130
|
catch (err) {
|
|
125
131
|
logError(err);
|
|
@@ -2,22 +2,21 @@ 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 { lib } from '../../lang/en.js';
|
|
6
|
-
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
7
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
8
5
|
import { uiLogger } from '../ui/logger.js';
|
|
6
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
7
|
+
import { lib } from '../../lang/en.js';
|
|
9
8
|
async function createProjectsList(accountId) {
|
|
10
|
-
if (!accountId) {
|
|
11
|
-
uiLogger.error(lib.prompts.downloadProjectPrompt.errors.accountIdRequired);
|
|
12
|
-
throw new PromptExitError(lib.prompts.downloadProjectPrompt.errors.accountIdRequired, EXIT_CODES.ERROR);
|
|
13
|
-
}
|
|
14
9
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
if (accountId) {
|
|
11
|
+
const { data: projects } = await fetchProjects(accountId);
|
|
12
|
+
return projects.results;
|
|
13
|
+
}
|
|
14
|
+
uiLogger.error(lib.prompts.downloadProjectPrompt.errors.accountIdRequired);
|
|
15
|
+
process.exit(EXIT_CODES.ERROR);
|
|
17
16
|
}
|
|
18
17
|
catch (e) {
|
|
19
|
-
logError(e, new ApiErrorContext({ accountId }));
|
|
20
|
-
|
|
18
|
+
logError(e, accountId ? new ApiErrorContext({ accountId }) : undefined);
|
|
19
|
+
process.exit(EXIT_CODES.ERROR);
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
export async function downloadProjectPrompt(promptOptions) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import open from 'open';
|
|
2
2
|
import { promptUser } from './promptUtils.js';
|
|
3
|
+
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
3
4
|
import { lib } from '../../lang/en.js';
|
|
4
5
|
import { uiLogger } from '../ui/logger.js';
|
|
5
|
-
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
6
|
-
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
7
6
|
export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
8
7
|
uiLogger.log('');
|
|
9
8
|
if (isReinstall) {
|
|
@@ -21,7 +20,7 @@ export async function installAppBrowserPrompt(installUrl, isReinstall = false) {
|
|
|
21
20
|
});
|
|
22
21
|
if (!isReinstall && !shouldOpenBrowser) {
|
|
23
22
|
uiLogger.log(lib.prompts.installAppPrompt.decline);
|
|
24
|
-
|
|
23
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
25
24
|
}
|
|
26
25
|
else if (!shouldOpenBrowser) {
|
|
27
26
|
return;
|
|
@@ -6,9 +6,8 @@ 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 { lib } from '../../lang/en.js';
|
|
10
|
-
import { PromptExitError } from '../errors/PromptExitError.js';
|
|
11
9
|
import { EXIT_CODES } from '../enums/exitCodes.js';
|
|
10
|
+
import { lib } from '../../lang/en.js';
|
|
12
11
|
/**
|
|
13
12
|
* Displays notification to user that we are about to open the browser,
|
|
14
13
|
* then opens their browser to the personal-access-key shortlink
|
|
@@ -28,7 +27,7 @@ export async function personalAccessKeyPrompt({ env, account, }) {
|
|
|
28
27
|
]);
|
|
29
28
|
if (!choice) {
|
|
30
29
|
deleteConfigFileIfEmpty();
|
|
31
|
-
|
|
30
|
+
process.exit(EXIT_CODES.SUCCESS);
|
|
32
31
|
}
|
|
33
32
|
if (choice ===
|
|
34
33
|
lib.prompts.personalAccessKeyPrompt.personalAccessKeyPromptChoices
|