@hubspot/cli 7.3.0 → 7.4.1-experimental.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/bin/cli.js CHANGED
@@ -43,6 +43,7 @@ const cmsCommand = require('../commands/cms');
43
43
  const feedbackCommand = require('../commands/feedback');
44
44
  const doctorCommand = require('../commands/doctor');
45
45
  const completionCommand = require('../commands/completion');
46
+ const appCommand = require('../commands/app');
46
47
  const notifier = updateNotifier({
47
48
  pkg: { ...pkg, name: '@hubspot/cli' },
48
49
  distTag: 'latest',
@@ -294,6 +295,7 @@ const argv = yargs
294
295
  .command(feedbackCommand)
295
296
  .command(doctorCommand)
296
297
  .command(completionCommand)
298
+ .command(appCommand)
297
299
  .help()
298
300
  .alias('h', 'help')
299
301
  .recommendCommands()
@@ -0,0 +1,7 @@
1
+ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
2
+ import { MigrateAppOptions } from '../../types/Yargs';
3
+ export declare const validMigrationTargets: string[];
4
+ export declare function handler(options: ArgumentsCamelCase<MigrateAppOptions>): Promise<never>;
5
+ export declare function builder(yargs: Argv): Argv<MigrateAppOptions>;
6
+ declare const migrateCommand: CommandModule<unknown, MigrateAppOptions>;
7
+ export default migrateCommand;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validMigrationTargets = void 0;
4
+ exports.handler = handler;
5
+ exports.builder = builder;
6
+ const commonOpts_1 = require("../../lib/commonOpts");
7
+ const usageTracking_1 = require("../../lib/usageTracking");
8
+ const lang_1 = require("../../lib/lang");
9
+ const errorHandlers_1 = require("../../lib/errorHandlers");
10
+ const exitCodes_1 = require("../../lib/enums/exitCodes");
11
+ const config_1 = require("@hubspot/local-dev-lib/config");
12
+ const migrate_1 = require("../../lib/app/migrate");
13
+ const projects_1 = require("@hubspot/local-dev-lib/constants/projects");
14
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
15
+ const ui_1 = require("../../lib/ui");
16
+ const { v2023_2, v2025_2, unstable } = projects_1.PLATFORM_VERSIONS;
17
+ exports.validMigrationTargets = [v2023_2, v2025_2, unstable];
18
+ const command = 'migrate';
19
+ const describe = undefined; // uiBetaTag(i18n(`commands.project.subcommands.migrateApp.header.text.describe`), false);
20
+ async function handler(options) {
21
+ const { derivedAccountId, platformVersion } = options;
22
+ await (0, usageTracking_1.trackCommandUsage)('migrate-app', {}, derivedAccountId);
23
+ const accountConfig = (0, config_1.getAccountConfig)(derivedAccountId);
24
+ if (!accountConfig) {
25
+ logger_1.logger.error((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.errors.noAccountConfig`));
26
+ return process.exit(exitCodes_1.EXIT_CODES.ERROR);
27
+ }
28
+ logger_1.logger.log('');
29
+ logger_1.logger.log((0, ui_1.uiBetaTag)((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.header.text`), false));
30
+ logger_1.logger.log((0, ui_1.uiLink)((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.header.link`), 'https://developers.hubspot.com/docs/platform/migrate-a-public-app-to-projects'));
31
+ logger_1.logger.log('');
32
+ try {
33
+ if (platformVersion === v2025_2 || platformVersion === unstable) {
34
+ await (0, migrate_1.migrateApp2025_2)(derivedAccountId, options);
35
+ }
36
+ else {
37
+ await (0, migrate_1.migrateApp2023_2)(derivedAccountId, options, accountConfig);
38
+ }
39
+ }
40
+ catch (error) {
41
+ if (error &&
42
+ typeof error === 'object' &&
43
+ 'errors' in error &&
44
+ Array.isArray(error.errors)) {
45
+ error.errors.forEach(err => (0, errorHandlers_1.logError)(err));
46
+ }
47
+ else {
48
+ (0, errorHandlers_1.logError)(error, new errorHandlers_1.ApiErrorContext({ accountId: derivedAccountId }));
49
+ }
50
+ await (0, usageTracking_1.trackCommandMetadataUsage)('migrate-app', { successful: false }, derivedAccountId);
51
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
52
+ }
53
+ await (0, usageTracking_1.trackCommandMetadataUsage)('migrate-app', { successful: true }, derivedAccountId);
54
+ return process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
55
+ }
56
+ function builder(yargs) {
57
+ (0, commonOpts_1.addConfigOptions)(yargs);
58
+ (0, commonOpts_1.addAccountOptions)(yargs);
59
+ (0, commonOpts_1.addUseEnvironmentOptions)(yargs);
60
+ yargs.options({
61
+ name: {
62
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.name.describe`),
63
+ type: 'string',
64
+ },
65
+ dest: {
66
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.dest.describe`),
67
+ type: 'string',
68
+ },
69
+ 'app-id': {
70
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.appId.describe`),
71
+ type: 'number',
72
+ },
73
+ 'platform-version': {
74
+ type: 'string',
75
+ choices: exports.validMigrationTargets,
76
+ hidden: true,
77
+ default: '2023.2',
78
+ },
79
+ });
80
+ yargs.example([
81
+ [
82
+ `$0 app migrate`,
83
+ (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.examples.default`),
84
+ ],
85
+ ]);
86
+ return yargs;
87
+ }
88
+ const migrateCommand = {
89
+ command,
90
+ describe,
91
+ handler,
92
+ builder,
93
+ };
94
+ exports.default = migrateCommand;
@@ -0,0 +1,6 @@
1
+ import { Argv, CommandModule } from 'yargs';
2
+ export declare const command: string[];
3
+ export declare const describe: undefined;
4
+ export declare function builder(yargs: Argv): Argv<{}>;
5
+ declare const appCommand: CommandModule;
6
+ export default appCommand;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.describe = exports.command = void 0;
7
+ exports.builder = builder;
8
+ const migrate_1 = __importDefault(require("./app/migrate"));
9
+ const commonOpts_1 = require("../lib/commonOpts");
10
+ exports.command = ['app', 'apps'];
11
+ // Keep the command hidden for now
12
+ exports.describe = undefined;
13
+ function builder(yargs) {
14
+ (0, commonOpts_1.addGlobalOptions)(yargs);
15
+ return yargs.command(migrate_1.default).demandCommand(1, '');
16
+ }
17
+ const appCommand = {
18
+ command: exports.command,
19
+ describe: exports.describe,
20
+ builder,
21
+ handler: () => { },
22
+ };
23
+ exports.default = appCommand;
@@ -1 +1,9 @@
1
- export {};
1
+ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
2
+ import { CloneAppArgs } from '../../types/Yargs';
3
+ export declare const command = "clone-app";
4
+ export declare const describe: string | undefined;
5
+ export declare const deprecated = true;
6
+ export declare const handler: (options: ArgumentsCamelCase<CloneAppArgs>) => Promise<never>;
7
+ export declare const builder: (yargs: Argv) => Argv<CloneAppArgs>;
8
+ declare const cloneAppCommand: CommandModule<unknown, CloneAppArgs>;
9
+ export default cloneAppCommand;
@@ -1,43 +1,46 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // @ts-nocheck
4
- const path = require('path');
5
- const fs = require('fs');
6
- const { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, } = require('../../lib/commonOpts');
7
- const { trackCommandUsage, trackCommandMetadataUsage, } = require('../../lib/usageTracking');
8
- const { i18n } = require('../../lib/lang');
9
- const { selectPublicAppPrompt, } = require('../../lib/prompts/selectPublicAppPrompt');
10
- const { createProjectPrompt, } = require('../../lib/prompts/createProjectPrompt');
11
- const { poll } = require('../../lib/polling');
12
- const { uiBetaTag, uiLine, uiCommandReference, uiAccountDescription, } = require('../../lib/ui');
13
- const SpinniesManager = require('../../lib/ui/SpinniesManager');
14
- const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index');
15
- const { EXIT_CODES } = require('../../lib/enums/exitCodes');
16
- const { isAppDeveloperAccount } = require('../../lib/accountTypes');
17
- const { writeProjectConfig } = require('../../lib/projects');
18
- const { PROJECT_CONFIG_FILE } = require('../../lib/constants');
19
- const { cloneApp, checkCloneStatus, downloadClonedProject, } = require('@hubspot/local-dev-lib/api/projects');
20
- const { getCwd, sanitizeFileName } = require('@hubspot/local-dev-lib/path');
21
- const { logger } = require('@hubspot/local-dev-lib/logger');
22
- const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
23
- const { extractZipArchive } = require('@hubspot/local-dev-lib/archive');
6
+ exports.builder = exports.handler = exports.deprecated = exports.describe = exports.command = void 0;
7
+ const ui_1 = require("../../lib/ui");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const commonOpts_1 = require("../../lib/commonOpts");
11
+ const usageTracking_1 = require("../../lib/usageTracking");
12
+ const lang_1 = require("../../lib/lang");
13
+ const selectPublicAppPrompt_1 = require("../../lib/prompts/selectPublicAppPrompt");
14
+ const createProjectPrompt_1 = require("../../lib/prompts/createProjectPrompt");
15
+ const polling_1 = require("../../lib/polling");
16
+ const ui_2 = require("../../lib/ui");
17
+ const errorHandlers_1 = require("../../lib/errorHandlers");
18
+ const exitCodes_1 = require("../../lib/enums/exitCodes");
19
+ const accountTypes_1 = require("../../lib/accountTypes");
20
+ const projects_1 = require("../../lib/projects");
21
+ const constants_1 = require("../../lib/constants");
22
+ const projects_2 = require("@hubspot/local-dev-lib/api/projects");
23
+ const path_2 = require("@hubspot/local-dev-lib/path");
24
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
25
+ const archive_1 = require("@hubspot/local-dev-lib/archive");
26
+ const config_1 = require("@hubspot/local-dev-lib/config");
27
+ const SpinniesManager_1 = __importDefault(require("../../lib/ui/SpinniesManager"));
28
+ const migrate_1 = require("../../lib/app/migrate");
24
29
  const i18nKey = 'commands.project.subcommands.cloneApp';
25
30
  exports.command = 'clone-app';
26
- exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
27
- exports.handler = async (options) => {
31
+ exports.describe = (0, ui_1.uiDeprecatedTag)((0, lang_1.i18n)(`${i18nKey}.describe`), false);
32
+ exports.deprecated = true;
33
+ const handler = async (options) => {
28
34
  const { derivedAccountId } = options;
29
- const accountConfig = getAccountConfig(derivedAccountId);
30
- const accountName = uiAccountDescription(derivedAccountId);
31
- trackCommandUsage('clone-app', {}, derivedAccountId);
32
- if (!isAppDeveloperAccount(accountConfig)) {
33
- uiLine();
34
- logger.error(i18n(`${i18nKey}.errors.invalidAccountTypeTitle`));
35
- logger.log(i18n(`${i18nKey}.errors.invalidAccountTypeDescription`, {
36
- useCommand: uiCommandReference('hs accounts use'),
37
- authCommand: uiCommandReference('hs auth'),
38
- }));
39
- uiLine();
40
- process.exit(EXIT_CODES.SUCCESS);
35
+ await (0, usageTracking_1.trackCommandUsage)('clone-app', {}, derivedAccountId);
36
+ const accountConfig = (0, config_1.getAccountConfig)(derivedAccountId);
37
+ const accountName = (0, ui_2.uiAccountDescription)(derivedAccountId);
38
+ if (!accountConfig) {
39
+ throw new Error((0, lang_1.i18n)(`commands.projects.subcommands.cloneApp.errors.noAccountConfig`));
40
+ }
41
+ if (!(0, accountTypes_1.isAppDeveloperAccount)(accountConfig)) {
42
+ (0, migrate_1.logInvalidAccountError)(i18nKey);
43
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
41
44
  }
42
45
  let appId;
43
46
  let projectName;
@@ -45,96 +48,108 @@ exports.handler = async (options) => {
45
48
  try {
46
49
  appId = options.appId;
47
50
  if (!appId) {
48
- const appIdResponse = await selectPublicAppPrompt({
51
+ const appIdResponse = await (0, selectPublicAppPrompt_1.selectPublicAppPrompt)({
49
52
  accountId: derivedAccountId,
50
53
  accountName,
51
- options,
52
54
  isMigratingApp: false,
53
55
  });
54
56
  appId = appIdResponse.appId;
55
57
  }
56
- const createProjectPromptResponse = await createProjectPrompt(options);
58
+ const createProjectPromptResponse = await (0, createProjectPrompt_1.createProjectPrompt)(options);
57
59
  projectName = createProjectPromptResponse.name;
58
60
  projectDest = createProjectPromptResponse.dest;
59
61
  }
60
62
  catch (error) {
61
- logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
62
- process.exit(EXIT_CODES.ERROR);
63
+ (0, errorHandlers_1.logError)(error, new errorHandlers_1.ApiErrorContext({ accountId: derivedAccountId }));
64
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
63
65
  }
64
- await trackCommandMetadataUsage('clone-app', { status: 'STARTED' }, derivedAccountId);
66
+ await (0, usageTracking_1.trackCommandMetadataUsage)('clone-app', { step: 'STARTED' }, derivedAccountId);
65
67
  try {
66
- SpinniesManager.init();
67
- SpinniesManager.add('cloneApp', {
68
- text: i18n(`${i18nKey}.cloneStatus.inProgress`),
68
+ SpinniesManager_1.default.init();
69
+ SpinniesManager_1.default.add('cloneApp', {
70
+ text: (0, lang_1.i18n)(`${i18nKey}.cloneStatus.inProgress`),
69
71
  });
70
- const { data: { exportId }, } = await cloneApp(derivedAccountId, appId);
71
- const { status } = await poll(() => checkCloneStatus(derivedAccountId, exportId));
72
+ const { data: { exportId }, } = await (0, projects_2.cloneApp)(derivedAccountId, appId);
73
+ const { status } = await (0, polling_1.poll)(() => (0, projects_2.checkCloneStatus)(derivedAccountId, exportId));
72
74
  if (status === 'SUCCESS') {
73
75
  // Ensure correct project folder structure exists
74
- const baseDestPath = path.resolve(getCwd(), projectDest);
75
- const absoluteDestPath = path.resolve(baseDestPath, 'src', 'app');
76
- fs.mkdirSync(absoluteDestPath, { recursive: true });
76
+ const baseDestPath = path_1.default.resolve((0, path_2.getCwd)(), projectDest);
77
+ const absoluteDestPath = path_1.default.resolve(baseDestPath, 'src', 'app');
78
+ fs_1.default.mkdirSync(absoluteDestPath, { recursive: true });
77
79
  // Extract zipped app files and place them in correct directory
78
- const { data: zippedApp } = await downloadClonedProject(derivedAccountId, exportId);
79
- await extractZipArchive(zippedApp, sanitizeFileName(projectName), absoluteDestPath, {
80
+ const { data: zippedApp } = await (0, projects_2.downloadClonedProject)(derivedAccountId, exportId);
81
+ await (0, archive_1.extractZipArchive)(zippedApp, (0, path_2.sanitizeFileName)(projectName), absoluteDestPath, {
80
82
  includesRootDir: true,
81
83
  hideLogs: true,
82
84
  });
83
85
  // Create hsproject.json file
84
- const configPath = path.join(baseDestPath, PROJECT_CONFIG_FILE);
86
+ const configPath = path_1.default.join(baseDestPath, constants_1.PROJECT_CONFIG_FILE);
85
87
  const configContent = {
86
88
  name: projectName,
87
89
  srcDir: 'src',
88
90
  platformVersion: '2023.2',
89
91
  };
90
- const success = writeProjectConfig(configPath, configContent);
91
- SpinniesManager.succeed('cloneApp', {
92
- text: i18n(`${i18nKey}.cloneStatus.done`),
92
+ const success = (0, projects_1.writeProjectConfig)(configPath, configContent);
93
+ SpinniesManager_1.default.succeed('cloneApp', {
94
+ text: (0, lang_1.i18n)(`${i18nKey}.cloneStatus.done`),
93
95
  succeedColor: 'white',
94
96
  });
95
97
  if (!success) {
96
- logger.error(i18n(`${i18nKey}.errors.couldNotWriteConfigPath`), configPath);
98
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.couldNotWriteConfigPath`), configPath);
97
99
  }
98
- logger.log('');
99
- uiLine();
100
- logger.success(i18n(`${i18nKey}.cloneStatus.success`, { dest }));
101
- logger.log('');
102
- process.exit(EXIT_CODES.SUCCESS);
100
+ logger_1.logger.log('');
101
+ (0, ui_2.uiLine)();
102
+ logger_1.logger.success((0, lang_1.i18n)(`${i18nKey}.cloneStatus.success`, { dest: projectDest }));
103
+ logger_1.logger.log('');
104
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
103
105
  }
104
106
  }
105
107
  catch (error) {
106
- await trackCommandMetadataUsage('clone-app', { status: 'FAILURE' }, derivedAccountId);
107
- SpinniesManager.fail('cloneApp', {
108
- text: i18n(`${i18nKey}.cloneStatus.failure`),
108
+ await (0, usageTracking_1.trackCommandMetadataUsage)('clone-app', { successful: false }, derivedAccountId);
109
+ SpinniesManager_1.default.fail('cloneApp', {
110
+ text: (0, lang_1.i18n)(`${i18nKey}.cloneStatus.failure`),
109
111
  failColor: 'white',
110
112
  });
111
113
  // Migrations endpoints return a response object with an errors property. The errors property contains an array of errors.
112
- if (error.errors && Array.isArray(error.errors)) {
113
- error.errors.forEach(e => logError(e, new ApiErrorContext({ accountId: derivedAccountId })));
114
+ if (error &&
115
+ typeof error === 'object' &&
116
+ 'errors' in error &&
117
+ Array.isArray(error.errors)) {
118
+ error.errors.forEach(e => (0, errorHandlers_1.logError)(e, new errorHandlers_1.ApiErrorContext({ accountId: derivedAccountId })));
114
119
  }
115
120
  else {
116
- logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
121
+ (0, errorHandlers_1.logError)(error, new errorHandlers_1.ApiErrorContext({ accountId: derivedAccountId }));
117
122
  }
118
123
  }
119
- await trackCommandMetadataUsage('clone-app', { status: 'SUCCESS' }, derivedAccountId);
120
- process.exit(EXIT_CODES.SUCCESS);
124
+ await (0, usageTracking_1.trackCommandMetadataUsage)('clone-app', { successful: true }, derivedAccountId);
125
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
121
126
  };
122
- exports.builder = yargs => {
127
+ exports.handler = handler;
128
+ const builder = (yargs) => {
123
129
  yargs.options({
124
130
  dest: {
125
- describe: i18n(`${i18nKey}.options.dest.describe`),
131
+ describe: (0, lang_1.i18n)(`${i18nKey}.options.dest.describe`),
126
132
  type: 'string',
127
133
  },
128
134
  'app-id': {
129
- describe: i18n(`${i18nKey}.options.appId.describe`),
135
+ describe: (0, lang_1.i18n)(`${i18nKey}.options.appId.describe`),
130
136
  type: 'number',
131
137
  },
132
138
  });
133
139
  yargs.example([
134
- ['$0 project clone-app', i18n(`${i18nKey}.examples.default`)],
140
+ ['$0 project clone-app', (0, lang_1.i18n)(`${i18nKey}.examples.default`)],
135
141
  ]);
136
- addConfigOptions(yargs);
137
- addAccountOptions(yargs);
138
- addUseEnvironmentOptions(yargs);
142
+ (0, commonOpts_1.addConfigOptions)(yargs);
143
+ (0, commonOpts_1.addAccountOptions)(yargs);
144
+ (0, commonOpts_1.addUseEnvironmentOptions)(yargs);
139
145
  return yargs;
140
146
  };
147
+ exports.builder = builder;
148
+ const cloneAppCommand = {
149
+ command: exports.command,
150
+ describe: exports.describe,
151
+ handler: exports.handler,
152
+ builder: exports.builder,
153
+ deprecated: exports.deprecated,
154
+ };
155
+ exports.default = cloneAppCommand;
@@ -1 +1,9 @@
1
- export {};
1
+ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
2
+ import { MigrateAppOptions } from '../../types/Yargs';
3
+ export declare const command = "migrate-app";
4
+ export declare const describe: string | undefined;
5
+ export declare const deprecated = true;
6
+ export declare function handler(yargs: ArgumentsCamelCase<MigrateAppOptions>): Promise<void>;
7
+ export declare function builder(yargs: Argv): Argv<MigrateAppOptions>;
8
+ declare const migrateAppCommand: CommandModule<unknown, MigrateAppOptions>;
9
+ export default migrateAppCommand;
@@ -1,188 +1,61 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // @ts-nocheck
4
- const path = require('path');
5
- const { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, } = require('../../lib/commonOpts');
6
- const { trackCommandUsage, trackCommandMetadataUsage, } = require('../../lib/usageTracking');
7
- const { createProjectPrompt, } = require('../../lib/prompts/createProjectPrompt');
8
- const { i18n } = require('../../lib/lang');
9
- const { selectPublicAppPrompt, } = require('../../lib/prompts/selectPublicAppPrompt');
10
- const { poll } = require('../../lib/polling');
11
- const { uiBetaTag, uiLine, uiLink, uiCommandReference, uiAccountDescription, } = require('../../lib/ui');
12
- const SpinniesManager = require('../../lib/ui/SpinniesManager');
13
- const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index');
14
- const { EXIT_CODES } = require('../../lib/enums/exitCodes');
15
- const { promptUser } = require('../../lib/prompts/promptUtils');
16
- const { isAppDeveloperAccount } = require('../../lib/accountTypes');
17
- const { ensureProjectExists } = require('../../lib/projects');
18
- const { handleKeypress } = require('../../lib/process');
19
- const { migrateApp, checkMigrationStatus, } = require('@hubspot/local-dev-lib/api/projects');
20
- const { getCwd, sanitizeFileName } = require('@hubspot/local-dev-lib/path');
21
- const { logger } = require('@hubspot/local-dev-lib/logger');
22
- const { getAccountConfig } = require('@hubspot/local-dev-lib/config');
23
- const { downloadProject } = require('@hubspot/local-dev-lib/api/projects');
24
- const { extractZipArchive } = require('@hubspot/local-dev-lib/archive');
25
- const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls');
26
- const { fetchPublicAppMetadata, } = require('@hubspot/local-dev-lib/api/appsDev');
27
- const i18nKey = 'commands.project.subcommands.migrateApp';
3
+ exports.deprecated = exports.describe = exports.command = void 0;
4
+ exports.handler = handler;
5
+ exports.builder = builder;
6
+ const lang_1 = require("../../lib/lang");
7
+ const ui_1 = require("../../lib/ui");
8
+ const migrate_1 = require("../app/migrate");
9
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
10
+ const commonOpts_1 = require("../../lib/commonOpts");
28
11
  exports.command = 'migrate-app';
29
- exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false);
30
- exports.handler = async (options) => {
31
- const { derivedAccountId } = options;
32
- const accountConfig = getAccountConfig(derivedAccountId);
33
- const accountName = uiAccountDescription(derivedAccountId);
34
- trackCommandUsage('migrate-app', {}, derivedAccountId);
35
- logger.log('');
36
- logger.log(uiBetaTag(i18n(`${i18nKey}.header.text`), false));
37
- logger.log(uiLink(i18n(`${i18nKey}.header.link`), 'https://developers.hubspot.com/docs/platform/migrate-a-public-app-to-projects'));
38
- logger.log('');
39
- if (!isAppDeveloperAccount(accountConfig)) {
40
- uiLine();
41
- logger.error(i18n(`${i18nKey}.errors.invalidAccountTypeTitle`));
42
- logger.log(i18n(`${i18nKey}.errors.invalidAccountTypeDescription`, {
43
- useCommand: uiCommandReference('hs accounts use'),
44
- authCommand: uiCommandReference('hs auth'),
45
- }));
46
- uiLine();
47
- process.exit(EXIT_CODES.SUCCESS);
48
- }
49
- const { appId } = 'appId' in options
50
- ? options
51
- : await selectPublicAppPrompt({
52
- accountId: derivedAccountId,
53
- accountName,
54
- isMigratingApp: true,
55
- });
56
- try {
57
- const { data: selectedApp } = await fetchPublicAppMetadata(appId, derivedAccountId);
58
- // preventProjectMigrations returns true if we have not added app to allowlist config.
59
- // listingInfo will only exist for marketplace apps
60
- const preventProjectMigrations = selectedApp.preventProjectMigrations;
61
- const listingInfo = selectedApp.listingInfo;
62
- if (preventProjectMigrations && listingInfo) {
63
- logger.error(i18n(`${i18nKey}.errors.invalidApp`, { appId }));
64
- process.exit(EXIT_CODES.ERROR);
65
- }
66
- }
67
- catch (error) {
68
- logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
69
- process.exit(EXIT_CODES.ERROR);
70
- }
71
- let projectName;
72
- let projectDest;
73
- try {
74
- const createProjectPromptResponse = await createProjectPrompt(options);
75
- projectName = createProjectPromptResponse.name;
76
- projectDest = createProjectPromptResponse.dest;
77
- const { projectExists } = await ensureProjectExists(derivedAccountId, projectName, {
78
- allowCreate: false,
79
- noLogs: true,
80
- });
81
- if (projectExists) {
82
- logger.error(i18n(`${i18nKey}.errors.projectAlreadyExists`, {
83
- projectName,
84
- }));
85
- process.exit(EXIT_CODES.ERROR);
86
- }
87
- }
88
- catch (error) {
89
- logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
90
- process.exit(EXIT_CODES.ERROR);
91
- }
92
- await trackCommandMetadataUsage('migrate-app', { status: 'STARTED' }, derivedAccountId);
93
- logger.log('');
94
- uiLine();
95
- logger.warn(i18n(`${i18nKey}.warning.title`));
96
- logger.log('');
97
- logger.log(i18n(`${i18nKey}.warning.projectConversion`));
98
- logger.log(i18n(`${i18nKey}.warning.appConfig`));
99
- logger.log('');
100
- logger.log(i18n(`${i18nKey}.warning.buildAndDeploy`));
101
- logger.log('');
102
- logger.log(i18n(`${i18nKey}.warning.existingApps`));
103
- logger.log('');
104
- logger.log(i18n(`${i18nKey}.warning.copyApp`));
105
- uiLine();
106
- const { shouldCreateApp } = await promptUser({
107
- name: 'shouldCreateApp',
108
- type: 'confirm',
109
- message: i18n(`${i18nKey}.createAppPrompt`),
110
- });
111
- process.stdin.resume();
112
- if (!shouldCreateApp) {
113
- process.exit(EXIT_CODES.SUCCESS);
114
- }
115
- try {
116
- SpinniesManager.init();
117
- SpinniesManager.add('migrateApp', {
118
- text: i18n(`${i18nKey}.migrationStatus.inProgress`),
119
- });
120
- handleKeypress(async (key) => {
121
- if ((key.ctrl && key.name === 'c') || key.name === 'q') {
122
- SpinniesManager.remove('migrateApp');
123
- logger.log(i18n(`${i18nKey}.migrationInterrupted`));
124
- process.exit(EXIT_CODES.SUCCESS);
125
- }
126
- });
127
- const { data: migrateResponse } = await migrateApp(derivedAccountId, appId, projectName);
128
- const { id } = migrateResponse;
129
- const pollResponse = await poll(() => checkMigrationStatus(derivedAccountId, id));
130
- const { status, project } = pollResponse;
131
- if (status === 'SUCCESS') {
132
- const absoluteDestPath = path.resolve(getCwd(), projectDest);
133
- const { env } = accountConfig;
134
- const baseUrl = getHubSpotWebsiteOrigin(env);
135
- const { data: zippedProject } = await downloadProject(derivedAccountId, projectName, 1);
136
- await extractZipArchive(zippedProject, sanitizeFileName(projectName), path.resolve(absoluteDestPath), { includesRootDir: true, hideLogs: true });
137
- SpinniesManager.succeed('migrateApp', {
138
- text: i18n(`${i18nKey}.migrationStatus.done`),
139
- succeedColor: 'white',
140
- });
141
- logger.log('');
142
- uiLine();
143
- logger.success(i18n(`${i18nKey}.migrationStatus.success`));
144
- logger.log('');
145
- logger.log(uiLink(i18n(`${i18nKey}.projectDetailsLink`), `${baseUrl}/developer-projects/${derivedAccountId}/project/${encodeURIComponent(project.name)}`));
146
- process.exit(EXIT_CODES.SUCCESS);
147
- }
148
- }
149
- catch (error) {
150
- await trackCommandMetadataUsage('migrate-app', { status: 'FAILURE' }, derivedAccountId);
151
- SpinniesManager.fail('migrateApp', {
152
- text: i18n(`${i18nKey}.migrationStatus.failure`),
153
- failColor: 'white',
154
- });
155
- if (error.errors) {
156
- error.errors.forEach(logError);
157
- }
158
- else {
159
- logError(error, new ApiErrorContext({ accountId: derivedAccountId }));
160
- }
161
- process.exit(EXIT_CODES.ERROR);
162
- }
163
- await trackCommandMetadataUsage('migrate-app', { status: 'SUCCESS' }, derivedAccountId);
164
- process.exit(EXIT_CODES.SUCCESS);
165
- };
166
- exports.builder = yargs => {
12
+ // TODO: Leave this as deprecated and remove in the next major release
13
+ exports.describe = (0, ui_1.uiDeprecatedTag)((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.describe`), false);
14
+ exports.deprecated = true;
15
+ async function handler(yargs) {
16
+ logger_1.logger.warn((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.deprecationWarning`, {
17
+ oldCommand: (0, ui_1.uiCommandReference)('hs project migrate-app'),
18
+ newCommand: (0, ui_1.uiCommandReference)('hs app migrate'),
19
+ }));
20
+ await (0, migrate_1.handler)(yargs);
21
+ }
22
+ function builder(yargs) {
23
+ (0, commonOpts_1.addConfigOptions)(yargs);
24
+ (0, commonOpts_1.addAccountOptions)(yargs);
25
+ (0, commonOpts_1.addUseEnvironmentOptions)(yargs);
167
26
  yargs.options({
168
27
  name: {
169
- describe: i18n(`${i18nKey}.options.name.describe`),
28
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.name.describe`),
170
29
  type: 'string',
171
30
  },
172
31
  dest: {
173
- describe: i18n(`${i18nKey}.options.dest.describe`),
32
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.dest.describe`),
174
33
  type: 'string',
175
34
  },
176
35
  'app-id': {
177
- describe: i18n(`${i18nKey}.options.appId.describe`),
36
+ describe: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.options.appId.describe`),
178
37
  type: 'number',
179
38
  },
39
+ 'platform-version': {
40
+ type: 'string',
41
+ choices: migrate_1.validMigrationTargets,
42
+ hidden: true,
43
+ default: '2023.2',
44
+ },
180
45
  });
181
46
  yargs.example([
182
- ['$0 project migrate-app', i18n(`${i18nKey}.examples.default`)],
47
+ [
48
+ `$0 project migrate-app`,
49
+ (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.examples.default`),
50
+ ],
183
51
  ]);
184
- addConfigOptions(yargs);
185
- addAccountOptions(yargs);
186
- addUseEnvironmentOptions(yargs);
187
52
  return yargs;
53
+ }
54
+ const migrateAppCommand = {
55
+ command: exports.command,
56
+ describe: exports.describe,
57
+ deprecated: exports.deprecated,
58
+ handler,
59
+ builder,
188
60
  };
61
+ exports.default = migrateAppCommand;
package/lang/en.lyaml CHANGED
@@ -570,6 +570,7 @@ en:
570
570
  header:
571
571
  text: "Migrate an app to the projects framework"
572
572
  link: "Learn more about migrating apps to the projects framework"
573
+ deprecationWarning: "The {{ oldCommand }} command is deprecated and will be removed. Use {{ newCommand }} going forward."
573
574
  migrationStatus:
574
575
  inProgress: "Converting app configuration to {{#bold}}public-app.json{{/bold}} component definition ..."
575
576
  success: "{{#bold}}Your app was converted and build #1 is deployed{{/bold}}"
@@ -585,11 +586,41 @@ en:
585
586
  migrationInterrupted: "\nThe command is terminated, but app migration is still in progress. Please check your account to ensure that the project and associated app have been created successfully."
586
587
  createAppPrompt: "Proceed with migrating this app to a project component (this process can't be aborted)?"
587
588
  projectDetailsLink: "View project details in your developer account"
589
+ componentsToBeMigrated: "The following component types will be migrated: {{ components }}"
590
+ componentsThatWillNotBeMigrated: "[NOTE] These component types are not yet supported for migration but will be available later: {{ components }}"
588
591
  errors:
592
+ noApps: "No apps found in account {{ accountId }}"
593
+ noAppsEligible: "No apps in account {{ accountId }} are currently migratable"
594
+ noAccountConfig: "There is no account associated with {{ accountId }} in the config file. Please choose another account and try again, or authenticate {{ accountId }} using {{ authCommand }}."
589
595
  invalidAccountTypeTitle: "{{#bold}}Developer account not targeted{{/bold}}"
590
596
  invalidAccountTypeDescription: "Only public apps created in a developer account can be converted to a project component. Select a connected developer account with {{useCommand}} or {{authCommand}} and try again."
591
597
  projectAlreadyExists: "A project with name {{ projectName }} already exists. Please choose another name."
592
598
  invalidApp: "Could not migrate appId {{ appId }}. This app cannot be migrated at this time. Please choose another public app."
599
+ appWithAppIdNotFound: "Could not find an app with the id {{ appId }} "
600
+ prompt:
601
+ chooseApp: 'Which app would you like to migrate?'
602
+ inputName: '[--name] What would you like to name the project?'
603
+ inputDest: '[--dest] Where would you like to save the project?'
604
+ uidForComponent: 'What UID would you like to use for {{ componentName }}?'
605
+ proceed: 'Would you like to proceed?'
606
+ spinners:
607
+ beginningMigration: "Beginning migration"
608
+ migrationStarted: "Migration started"
609
+ unableToStartMigration: "Unable to begin migration"
610
+ finishingMigration: "Wrapping up migration"
611
+ migrationComplete: "Migration completed"
612
+ migrationFailed: "Migration failed"
613
+ downloadingProjectContents: "Downloading migrated project files"
614
+ downloadingProjectContentsComplete: "Migrated project files downloaded"
615
+ downloadingProjectContentsFailed: "Unable to download migrated project files"
616
+ copyingProjectFiles: "Copying migrated project files"
617
+ copyingProjectFilesComplete: "Migrated project files copied"
618
+ copyingProjectFilesFailed: "Unable to copy migrated project files"
619
+ unmigratableReasons:
620
+ upToDate: 'App is already up to date'
621
+ isPrivateApp: 'Private apps are not currently migratable'
622
+ listedInMarketplace: 'Listed apps are not currently migratable'
623
+ generic: "Unable to migrate app: {{ reasonCode }}"
593
624
  cloneApp:
594
625
  describe: "Clone a public app using the projects framework."
595
626
  examples:
@@ -608,6 +639,7 @@ en:
608
639
  invalidAccountTypeTitle: "{{#bold}}Developer account not targeted{{/bold}}"
609
640
  invalidAccountTypeDescription: "Only public apps created in a developer account can be converted to a project component. Select a connected developer account with {{useCommand}} or {{authCommand}} and try again."
610
641
  couldNotWriteConfigPath: "Failed to write project config at {{ configPath }}"
642
+ noAccountConfig: "There is no account associated with {{ accountId }} in the config file. Please choose another account and try again, or authenticate {{ accountId }} using {{ authCommand }}."
611
643
  add:
612
644
  describe: "Create a new component within a project."
613
645
  options:
@@ -0,0 +1,7 @@
1
+ import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts';
2
+ import { ArgumentsCamelCase } from 'yargs';
3
+ import { MigrateAppOptions } from '../../types/Yargs';
4
+ export declare function downloadProjectFiles(derivedAccountId: number, projectName: string, buildId: number, projectDest: string): Promise<void>;
5
+ export declare function migrateApp2025_2(derivedAccountId: number, options: ArgumentsCamelCase<MigrateAppOptions>): Promise<void>;
6
+ export declare function logInvalidAccountError(i18nKey: string): void;
7
+ export declare function migrateApp2023_2(derivedAccountId: number, options: ArgumentsCamelCase<MigrateAppOptions>, accountConfig: CLIAccount): Promise<void>;
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadProjectFiles = downloadProjectFiles;
7
+ exports.migrateApp2025_2 = migrateApp2025_2;
8
+ exports.logInvalidAccountError = logInvalidAccountError;
9
+ exports.migrateApp2023_2 = migrateApp2023_2;
10
+ const promptUtils_1 = require("../prompts/promptUtils");
11
+ const errorHandlers_1 = require("../errorHandlers");
12
+ const exitCodes_1 = require("../enums/exitCodes");
13
+ const logger_1 = require("@hubspot/local-dev-lib/logger");
14
+ const ui_1 = require("../ui");
15
+ const lang_1 = require("../lang");
16
+ const accountTypes_1 = require("../accountTypes");
17
+ const selectPublicAppPrompt_1 = require("../prompts/selectPublicAppPrompt");
18
+ const appsDev_1 = require("@hubspot/local-dev-lib/api/appsDev");
19
+ const createProjectPrompt_1 = require("../prompts/createProjectPrompt");
20
+ const projects_1 = require("../projects");
21
+ const usageTracking_1 = require("../usageTracking");
22
+ const SpinniesManager_1 = __importDefault(require("../ui/SpinniesManager"));
23
+ const process_1 = require("../process");
24
+ const projects_2 = require("@hubspot/local-dev-lib/api/projects");
25
+ const polling_1 = require("../polling");
26
+ const path_1 = __importDefault(require("path"));
27
+ const path_2 = require("@hubspot/local-dev-lib/path");
28
+ const urls_1 = require("@hubspot/local-dev-lib/urls");
29
+ const archive_1 = require("@hubspot/local-dev-lib/archive");
30
+ const chalk_1 = __importDefault(require("chalk"));
31
+ const project_parsing_lib_1 = require("@hubspot/project-parsing-lib");
32
+ const projects_3 = require("@hubspot/local-dev-lib/constants/projects");
33
+ const transform_1 = require("@hubspot/project-parsing-lib/src/lib/transform");
34
+ function getUnmigratableReason(reasonCode) {
35
+ switch (reasonCode) {
36
+ case projects_3.UNMIGRATABLE_REASONS.UP_TO_DATE:
37
+ return (0, lang_1.i18n)('commands.project.subcommands.migrateApp.unmigratableReasons.upToDate');
38
+ case projects_3.UNMIGRATABLE_REASONS.IS_A_PRIVATE_APP:
39
+ return (0, lang_1.i18n)('commands.project.subcommands.migrateApp.unmigratableReasons.isPrivateApp');
40
+ case projects_3.UNMIGRATABLE_REASONS.LISTED_IN_MARKETPLACE:
41
+ return (0, lang_1.i18n)('commands.project.subcommands.migrateApp.unmigratableReasons.listedInMarketplace');
42
+ default:
43
+ return (0, lang_1.i18n)('commands.project.subcommands.migrateApp.unmigratableReasons.generic', {
44
+ reasonCode,
45
+ });
46
+ }
47
+ }
48
+ async function handleMigrationSetup(derivedAccountId, options) {
49
+ const { name, dest, appId } = options;
50
+ const { data } = await (0, projects_2.listAppsForMigration)(derivedAccountId);
51
+ const { migratableApps, unmigratableApps } = data;
52
+ const allApps = [...migratableApps, ...unmigratableApps].filter(app => !app.projectName);
53
+ if (allApps.length === 0) {
54
+ throw new Error((0, lang_1.i18n)(`commands.project.subcommands.migrateApp.errors.noApps`, {
55
+ accountId: derivedAccountId,
56
+ }));
57
+ }
58
+ if (migratableApps.length === 0) {
59
+ const reasons = unmigratableApps.map(app => `${chalk_1.default.bold(app.appName)}: ${getUnmigratableReason(app.unmigratableReason)}`);
60
+ throw new Error(`${(0, lang_1.i18n)(`commands.project.subcommands.migrateApp.errors.noAppsEligible`, {
61
+ accountId: derivedAccountId,
62
+ })} \n - ${reasons.join('\n - ')}`);
63
+ }
64
+ if (appId &&
65
+ !allApps.some(app => {
66
+ return app.appId === appId;
67
+ })) {
68
+ throw new Error((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.chooseApp', {
69
+ appId,
70
+ }));
71
+ }
72
+ const appChoices = allApps.map(app => ({
73
+ name: app.isMigratable
74
+ ? app.appName
75
+ : `[${chalk_1.default.yellow('DISABLED')}] ${app.appName} `,
76
+ value: app,
77
+ disabled: app.isMigratable
78
+ ? false
79
+ : getUnmigratableReason(app.unmigratableReason),
80
+ }));
81
+ let appIdToMigrate = appId;
82
+ if (!appIdToMigrate) {
83
+ const { appId: selectedAppId } = await (0, promptUtils_1.listPrompt)((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.chooseApp'), {
84
+ choices: appChoices,
85
+ });
86
+ appIdToMigrate = selectedAppId;
87
+ }
88
+ const selectedApp = allApps.find(app => app.appId === appIdToMigrate);
89
+ const migratableComponents = [];
90
+ const unmigratableComponents = [];
91
+ selectedApp?.migrationComponents.forEach(component => {
92
+ if (component.isSupported) {
93
+ migratableComponents.push((0, transform_1.mapToUserFacingType)(component.componentType));
94
+ }
95
+ else {
96
+ unmigratableComponents.push((0, transform_1.mapToUserFacingType)(component.componentType));
97
+ }
98
+ });
99
+ if (migratableComponents.length !== 0) {
100
+ logger_1.logger.log((0, lang_1.i18n)('commands.project.subcommands.migrateApp.componentsToBeMigrated', {
101
+ components: `\n - ${migratableComponents.join('\n - ')}`,
102
+ }));
103
+ }
104
+ if (unmigratableComponents.length !== 0) {
105
+ logger_1.logger.log((0, lang_1.i18n)('commands.project.subcommands.migrateApp.componentsThatWillNotBeMigrated', {
106
+ components: `\n - ${unmigratableComponents.join('\n - ')}`,
107
+ }));
108
+ }
109
+ logger_1.logger.log();
110
+ const proceed = await (0, promptUtils_1.confirmPrompt)((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.proceed'));
111
+ if (!proceed) {
112
+ return {};
113
+ }
114
+ const projectName = name ||
115
+ (await (0, promptUtils_1.inputPrompt)((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.inputName')));
116
+ const { projectExists } = await (0, projects_1.ensureProjectExists)(derivedAccountId, projectName, { forceCreate: false, allowCreate: false, noLogs: true });
117
+ if (projectExists) {
118
+ throw new Error((0, lang_1.i18n)('commands.project.subcommands.migrateApp.errors.projectAlreadyExists', {
119
+ projectName,
120
+ }));
121
+ }
122
+ const projectDest = dest ||
123
+ (await (0, promptUtils_1.inputPrompt)((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.inputDest'), {
124
+ defaultAnswer: path_1.default.resolve((0, path_2.getCwd)(), (0, path_2.sanitizeFileName)(projectName)),
125
+ }));
126
+ return { appIdToMigrate, projectName, projectDest };
127
+ }
128
+ async function beginMigration(derivedAccountId, appId, platformVersion) {
129
+ SpinniesManager_1.default.add('beginningMigration', {
130
+ text: (0, lang_1.i18n)('commands.project.subcommands.migrateApp.spinners.beginningMigration'),
131
+ });
132
+ const uidMap = {};
133
+ const { data } = await (0, projects_2.initializeMigration)(derivedAccountId, appId, platformVersion);
134
+ const { migrationId } = data;
135
+ const pollResponse = await pollMigrationStatus(derivedAccountId, migrationId, ['INPUT_REQUIRED']);
136
+ if (pollResponse.status !== 'INPUT_REQUIRED') {
137
+ SpinniesManager_1.default.fail('beginningMigration', {
138
+ text: (0, lang_1.i18n)('commands.project.subcommands.migrateApp.spinners.unableToStartMigration'),
139
+ });
140
+ return;
141
+ }
142
+ const { componentsRequiringUids } = pollResponse;
143
+ SpinniesManager_1.default.succeed('beginningMigration', {
144
+ text: (0, lang_1.i18n)('commands.project.subcommands.migrateApp.spinners.migrationStarted'),
145
+ });
146
+ if (Object.values(componentsRequiringUids).length !== 0) {
147
+ for (const [componentId, component] of Object.entries(componentsRequiringUids)) {
148
+ uidMap[componentId] = await (0, promptUtils_1.inputPrompt)((0, lang_1.i18n)('commands.project.subcommands.migrateApp.prompt.uidForComponent', {
149
+ componentName: component.componentHint || component.componentType,
150
+ }), {
151
+ validate: (uid) => {
152
+ const result = (0, project_parsing_lib_1.validateUid)(uid);
153
+ return result === undefined ? true : result;
154
+ },
155
+ });
156
+ }
157
+ }
158
+ return { migrationId, uidMap };
159
+ }
160
+ async function pollMigrationStatus(derivedAccountId, migrationId, successStates = []) {
161
+ return (0, polling_1.poll)(() => (0, projects_2.checkMigrationStatusV2)(derivedAccountId, migrationId), {
162
+ successStates: [...successStates],
163
+ errorStates: [...polling_1.DEFAULT_POLLING_STATUS_LOOKUP.errorStates],
164
+ });
165
+ }
166
+ async function finalizeMigration(derivedAccountId, migrationId, uidMap, projectName) {
167
+ let buildId;
168
+ try {
169
+ SpinniesManager_1.default.add('finishingMigration', {
170
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.finishingMigration`),
171
+ });
172
+ await (0, projects_2.continueMigration)(derivedAccountId, migrationId, uidMap, projectName);
173
+ const pollResponse = await pollMigrationStatus(derivedAccountId, migrationId, ['SUCCESS']);
174
+ if (pollResponse.status === 'SUCCESS') {
175
+ buildId = pollResponse.buildId;
176
+ SpinniesManager_1.default.succeed('finishingMigration', {
177
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.migrationComplete`),
178
+ });
179
+ }
180
+ else {
181
+ SpinniesManager_1.default.fail('finishingMigration', {
182
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.migrationFailed`),
183
+ });
184
+ }
185
+ return buildId;
186
+ }
187
+ catch (error) {
188
+ SpinniesManager_1.default.fail('finishingMigration', {
189
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.migrationFailed`),
190
+ });
191
+ throw error;
192
+ }
193
+ }
194
+ async function downloadProjectFiles(derivedAccountId, projectName, buildId, projectDest) {
195
+ try {
196
+ SpinniesManager_1.default.add('fetchingMigratedProject', {
197
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.downloadingProjectContents`),
198
+ });
199
+ const { data: zippedProject } = await (0, projects_2.downloadProject)(derivedAccountId, projectName, buildId);
200
+ const absoluteDestPath = projectDest
201
+ ? path_1.default.resolve((0, path_2.getCwd)(), projectDest)
202
+ : (0, path_2.getCwd)();
203
+ await (0, archive_1.extractZipArchive)(zippedProject, (0, path_2.sanitizeFileName)(projectName), absoluteDestPath, {
204
+ includesRootDir: true,
205
+ hideLogs: false,
206
+ });
207
+ SpinniesManager_1.default.succeed('fetchingMigratedProject', {
208
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.downloadingProjectContentsComplete`),
209
+ });
210
+ logger_1.logger.success(`Saved ${projectName} to ${projectDest}`);
211
+ }
212
+ catch (error) {
213
+ SpinniesManager_1.default.fail('fetchingMigratedProject', {
214
+ text: (0, lang_1.i18n)(`commands.project.subcommands.migrateApp.spinners.downloadingProjectContentsFailed`),
215
+ });
216
+ throw error;
217
+ }
218
+ }
219
+ async function migrateApp2025_2(derivedAccountId, options) {
220
+ SpinniesManager_1.default.init();
221
+ const { appIdToMigrate, projectName, projectDest } = await handleMigrationSetup(derivedAccountId, options);
222
+ if (!appIdToMigrate || !projectName || !projectDest) {
223
+ return;
224
+ }
225
+ const migrationInProgress = await beginMigration(derivedAccountId, appIdToMigrate, options.platformVersion);
226
+ if (!migrationInProgress) {
227
+ return;
228
+ }
229
+ const { migrationId, uidMap } = migrationInProgress;
230
+ const buildId = await finalizeMigration(derivedAccountId, migrationId, uidMap, projectName);
231
+ if (!buildId) {
232
+ throw new Error('Migration Failed');
233
+ }
234
+ await downloadProjectFiles(derivedAccountId, projectName, buildId, projectDest);
235
+ }
236
+ function logInvalidAccountError(i18nKey) {
237
+ (0, ui_1.uiLine)();
238
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.invalidAccountTypeTitle`));
239
+ logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.errors.invalidAccountTypeDescription`, {
240
+ useCommand: (0, ui_1.uiCommandReference)('hs accounts use'),
241
+ authCommand: (0, ui_1.uiCommandReference)('hs auth'),
242
+ }));
243
+ (0, ui_1.uiLine)();
244
+ }
245
+ async function migrateApp2023_2(derivedAccountId, options, accountConfig) {
246
+ const i18nKey = 'commands.project.subcommands.migrateApp';
247
+ const accountName = (0, ui_1.uiAccountDescription)(derivedAccountId);
248
+ if (!(0, accountTypes_1.isAppDeveloperAccount)(accountConfig)) {
249
+ logInvalidAccountError(i18nKey);
250
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
251
+ }
252
+ const { appId } = 'appId' in options
253
+ ? options
254
+ : await (0, selectPublicAppPrompt_1.selectPublicAppPrompt)({
255
+ accountId: derivedAccountId,
256
+ accountName,
257
+ isMigratingApp: true,
258
+ });
259
+ try {
260
+ const { data: selectedApp } = await (0, appsDev_1.fetchPublicAppMetadata)(appId, derivedAccountId);
261
+ // preventProjectMigrations returns true if we have not added app to allowlist config.
262
+ // listingInfo will only exist for marketplace apps
263
+ const preventProjectMigrations = selectedApp.preventProjectMigrations;
264
+ const listingInfo = selectedApp.listingInfo;
265
+ if (preventProjectMigrations && listingInfo) {
266
+ logger_1.logger.error((0, lang_1.i18n)(`${i18nKey}.errors.invalidApp`, { appId }));
267
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
268
+ }
269
+ }
270
+ catch (error) {
271
+ (0, errorHandlers_1.logError)(error, new errorHandlers_1.ApiErrorContext({ accountId: derivedAccountId }));
272
+ process.exit(exitCodes_1.EXIT_CODES.ERROR);
273
+ }
274
+ const createProjectPromptResponse = await (0, createProjectPrompt_1.createProjectPrompt)(options);
275
+ const { name: projectName, dest: projectDest } = createProjectPromptResponse;
276
+ const { projectExists } = await (0, projects_1.ensureProjectExists)(derivedAccountId, projectName, {
277
+ allowCreate: false,
278
+ noLogs: true,
279
+ });
280
+ if (projectExists) {
281
+ throw new Error((0, lang_1.i18n)(`${i18nKey}.errors.projectAlreadyExists`, {
282
+ projectName,
283
+ }));
284
+ }
285
+ await (0, usageTracking_1.trackCommandMetadataUsage)('migrate-app', { step: 'STARTED' }, derivedAccountId);
286
+ logger_1.logger.log('');
287
+ (0, ui_1.uiLine)();
288
+ logger_1.logger.warn(`${(0, lang_1.i18n)(`${i18nKey}.warning.title`)}\n`);
289
+ logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.warning.projectConversion`));
290
+ logger_1.logger.log(`${(0, lang_1.i18n)(`${i18nKey}.warning.appConfig`)}\n`);
291
+ logger_1.logger.log(`${(0, lang_1.i18n)(`${i18nKey}.warning.buildAndDeploy`)}\n`);
292
+ logger_1.logger.log(`${(0, lang_1.i18n)(`${i18nKey}.warning.existingApps`)}\n`);
293
+ logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.warning.copyApp`));
294
+ (0, ui_1.uiLine)();
295
+ const { shouldCreateApp } = await (0, promptUtils_1.promptUser)({
296
+ name: 'shouldCreateApp',
297
+ type: 'confirm',
298
+ message: (0, lang_1.i18n)(`${i18nKey}.createAppPrompt`),
299
+ });
300
+ process.stdin.resume();
301
+ if (!shouldCreateApp) {
302
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
303
+ }
304
+ try {
305
+ SpinniesManager_1.default.init();
306
+ SpinniesManager_1.default.add('migrateApp', {
307
+ text: (0, lang_1.i18n)(`${i18nKey}.migrationStatus.inProgress`),
308
+ });
309
+ (0, process_1.handleKeypress)(async (key) => {
310
+ if ((key.ctrl && key.name === 'c') || key.name === 'q') {
311
+ SpinniesManager_1.default.remove('migrateApp');
312
+ logger_1.logger.log((0, lang_1.i18n)(`${i18nKey}.migrationInterrupted`));
313
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
314
+ }
315
+ });
316
+ const { data: migrateResponse } = await (0, projects_2.migrateApp)(derivedAccountId, appId, projectName);
317
+ const { id } = migrateResponse;
318
+ const pollResponse = await (0, polling_1.poll)(() => (0, projects_2.checkMigrationStatus)(derivedAccountId, id));
319
+ const { status, project } = pollResponse;
320
+ if (status === 'SUCCESS') {
321
+ const absoluteDestPath = path_1.default.resolve((0, path_2.getCwd)(), projectDest);
322
+ const { env } = accountConfig;
323
+ const baseUrl = (0, urls_1.getHubSpotWebsiteOrigin)(env);
324
+ const { data: zippedProject } = await (0, projects_2.downloadProject)(derivedAccountId, projectName, 1);
325
+ await (0, archive_1.extractZipArchive)(zippedProject, (0, path_2.sanitizeFileName)(projectName), path_1.default.resolve(absoluteDestPath), { includesRootDir: true, hideLogs: true });
326
+ SpinniesManager_1.default.succeed('migrateApp', {
327
+ text: (0, lang_1.i18n)(`${i18nKey}.migrationStatus.done`),
328
+ succeedColor: 'white',
329
+ });
330
+ logger_1.logger.log('');
331
+ (0, ui_1.uiLine)();
332
+ logger_1.logger.success((0, lang_1.i18n)(`${i18nKey}.migrationStatus.success`));
333
+ logger_1.logger.log('');
334
+ logger_1.logger.log((0, ui_1.uiLink)((0, lang_1.i18n)(`${i18nKey}.projectDetailsLink`), `${baseUrl}/developer-projects/${derivedAccountId}/project/${encodeURIComponent(project.name)}`));
335
+ process.exit(exitCodes_1.EXIT_CODES.SUCCESS);
336
+ }
337
+ }
338
+ catch (error) {
339
+ SpinniesManager_1.default.fail('migrateApp', {
340
+ text: (0, lang_1.i18n)(`${i18nKey}.migrationStatus.failure`),
341
+ failColor: 'white',
342
+ });
343
+ throw error;
344
+ }
345
+ }
package/lib/polling.d.ts CHANGED
@@ -6,6 +6,10 @@ export declare const DEFAULT_POLLING_STATES: {
6
6
  readonly REVERTED: "REVERTED";
7
7
  readonly FAILURE: "FAILURE";
8
8
  };
9
+ export declare const DEFAULT_POLLING_STATUS_LOOKUP: {
10
+ successStates: "SUCCESS"[];
11
+ errorStates: ("FAILURE" | "ERROR" | "REVERTED")[];
12
+ };
9
13
  type GenericPollingResponse = {
10
14
  status: string;
11
15
  };
package/lib/polling.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_POLLING_STATES = void 0;
3
+ exports.DEFAULT_POLLING_STATUS_LOOKUP = exports.DEFAULT_POLLING_STATES = void 0;
4
4
  exports.poll = poll;
5
5
  const constants_1 = require("./constants");
6
6
  exports.DEFAULT_POLLING_STATES = {
@@ -10,7 +10,7 @@ exports.DEFAULT_POLLING_STATES = {
10
10
  REVERTED: 'REVERTED',
11
11
  FAILURE: 'FAILURE',
12
12
  };
13
- const DEFAULT_POLLING_STATUS_LOOKUP = {
13
+ exports.DEFAULT_POLLING_STATUS_LOOKUP = {
14
14
  successStates: [exports.DEFAULT_POLLING_STATES.SUCCESS],
15
15
  errorStates: [
16
16
  exports.DEFAULT_POLLING_STATES.ERROR,
@@ -18,7 +18,7 @@ const DEFAULT_POLLING_STATUS_LOOKUP = {
18
18
  exports.DEFAULT_POLLING_STATES.FAILURE,
19
19
  ],
20
20
  };
21
- function poll(callback, statusLookup = DEFAULT_POLLING_STATUS_LOOKUP) {
21
+ function poll(callback, statusLookup = exports.DEFAULT_POLLING_STATUS_LOOKUP) {
22
22
  return new Promise((resolve, reject) => {
23
23
  const pollInterval = setInterval(async () => {
24
24
  try {
@@ -4,10 +4,12 @@ export declare function confirmPrompt(message: string, options?: {
4
4
  defaultAnswer?: boolean;
5
5
  when?: PromptWhen;
6
6
  }): Promise<boolean>;
7
- export declare function listPrompt(message: string, { choices, when, }: {
8
- choices: PromptChoices;
7
+ export declare function listPrompt<T = string>(message: string, { choices, when, }: {
8
+ choices: PromptChoices<T>;
9
9
  when?: PromptWhen;
10
- }): Promise<string>;
11
- export declare function inputPrompt(message: string, { when, }?: {
10
+ }): Promise<T>;
11
+ export declare function inputPrompt(message: string, { when, validate, defaultAnswer, }?: {
12
12
  when?: boolean | (() => boolean);
13
+ validate?: (input: string) => boolean | string;
14
+ defaultAnswer?: string;
13
15
  }): Promise<string>;
@@ -34,13 +34,15 @@ async function listPrompt(message, { choices, when, }) {
34
34
  ]);
35
35
  return choice;
36
36
  }
37
- async function inputPrompt(message, { when, } = {}) {
37
+ async function inputPrompt(message, { when, validate, defaultAnswer, } = {}) {
38
38
  const { input } = await promptUser([
39
39
  {
40
40
  name: 'input',
41
41
  type: 'input',
42
+ default: defaultAnswer,
42
43
  message,
43
44
  when,
45
+ validate,
44
46
  },
45
47
  ]);
46
48
  return input;
package/lib/ui/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare function uiCommandReference(command: string, withQuotes?: boolean
11
11
  export declare function uiFeatureHighlight(features: string[], title?: string): void;
12
12
  export declare function uiBetaTag(message: string, log?: true): undefined;
13
13
  export declare function uiBetaTag(message: string, log: false): string;
14
- export declare function uiDeprecatedTag(message: string): void;
14
+ export declare function uiDeprecatedTag(message: string, log?: boolean): string | undefined;
15
15
  export declare function uiCommandDisabledBanner(command: string, url?: string, message?: string): void;
16
- export declare function uiDeprecatedDescription(message: string, command: string, url?: string): void;
16
+ export declare function uiDeprecatedDescription(message: string, command: string, url?: string): string | undefined;
17
17
  export declare function uiDeprecatedMessage(command: string, url?: string, message?: string): void;
package/lib/ui/index.js CHANGED
@@ -102,16 +102,17 @@ function uiBetaTag(message, log = true) {
102
102
  logger_1.logger.log(result);
103
103
  return;
104
104
  }
105
- else {
106
- return result;
107
- }
105
+ return result;
108
106
  }
109
- function uiDeprecatedTag(message) {
107
+ function uiDeprecatedTag(message, log = true) {
110
108
  const i18nKey = 'lib.ui';
111
109
  const terminalUISupport = getTerminalUISupport();
112
110
  const tag = (0, lang_1.i18n)(`${i18nKey}.deprecatedTag`);
113
111
  const result = `${terminalUISupport.color ? chalk_1.default.yellow(tag) : tag} ${message}`;
114
- logger_1.logger.log(result);
112
+ if (log) {
113
+ logger_1.logger.log(result);
114
+ }
115
+ return result;
115
116
  }
116
117
  function uiCommandDisabledBanner(command, url, message) {
117
118
  const i18nKey = 'lib.ui';
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@hubspot/cli",
3
- "version": "7.3.0",
3
+ "version": "7.4.1-experimental.0",
4
4
  "description": "The official CLI for developing on HubSpot",
5
5
  "license": "Apache-2.0",
6
6
  "repository": "https://github.com/HubSpot/hubspot-cli",
7
7
  "dependencies": {
8
- "@hubspot/local-dev-lib": "3.4.1",
8
+ "@hubspot/local-dev-lib": "0.3.0-experimental.0",
9
9
  "@hubspot/project-parsing-lib": "0.1.5",
10
10
  "@hubspot/serverless-dev-runtime": "7.0.2",
11
11
  "@hubspot/theme-preview-dev-server": "0.0.10",
@@ -3,9 +3,9 @@ export type GenericPromptResponse = {
3
3
  [key: string]: any;
4
4
  };
5
5
  type PromptType = 'confirm' | 'list' | 'checkbox' | 'input' | 'password' | 'number' | 'rawlist';
6
- export type PromptChoices = Array<string | {
6
+ export type PromptChoices<T = any> = Array<string | {
7
7
  name: string;
8
- value?: any;
8
+ value?: T;
9
9
  disabled?: string | boolean;
10
10
  }>;
11
11
  export type PromptWhen = boolean | (() => boolean);
package/types/Yargs.d.ts CHANGED
@@ -27,3 +27,13 @@ export type ProjectDevArgs = CommonArgs & ConfigArgs & EnvironmentArgs;
27
27
  export type TestingArgs = {
28
28
  qa?: boolean;
29
29
  };
30
+ export type MigrateAppOptions = CommonArgs & AccountArgs & EnvironmentArgs & ConfigArgs & {
31
+ name: string;
32
+ dest: string;
33
+ appId: number;
34
+ platformVersion: string;
35
+ };
36
+ export type CloneAppArgs = ConfigArgs & EnvironmentArgs & AccountArgs & CommonArgs & {
37
+ dest: string;
38
+ appId: number;
39
+ };