From 2a5f0ae7a1221ffb2e0afa200f79f0b1260f686a Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Mon, 2 Dec 2024 13:45:49 -0500 Subject: [PATCH 01/15] Convert prompts to TS p1 --- lib/prompts/accountsPrompt.ts | 39 +++++++++++++++++------------------ lib/prompts/cmsFieldPrompt.ts | 37 +++++++++++++++++---------------- lib/ui/index.ts | 5 ++++- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/lib/prompts/accountsPrompt.ts b/lib/prompts/accountsPrompt.ts index 573c367f2..21a54515c 100644 --- a/lib/prompts/accountsPrompt.ts +++ b/lib/prompts/accountsPrompt.ts @@ -1,24 +1,27 @@ -// @ts-nocheck -const { +import { getConfigDefaultAccount, getConfigAccounts, -} = require('@hubspot/local-dev-lib/config'); -const { - getAccountIdentifier, -} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { uiAccountDescription } = require('../ui'); +} from '@hubspot/local-dev-lib/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { uiAccountDescription } from '../ui'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; -const mapAccountChoices = portals => - portals.map(p => ({ - name: uiAccountDescription(getAccountIdentifier(p), false), - value: p.name || getAccountIdentifier(p), - })); +function mapAccountChoices( + portals: CLIAccount[] | null | undefined +): { name: string | undefined; value: string }[] | [] { + return portals + ? portals.map(p => ({ + name: uiAccountDescription(getAccountIdentifier(p), false), + value: String(p.name || getAccountIdentifier(p)), + })) + : []; +} const i18nKey = 'commands.account.subcommands.use'; -const selectAccountFromConfig = async (prompt = '') => { +export async function selectAccountFromConfig(prompt = ''): Promise { const accountsList = getConfigAccounts(); const defaultAccount = getConfigDefaultAccount(); @@ -35,8 +38,4 @@ const selectAccountFromConfig = async (prompt = '') => { ]); return selectedDefault; -}; - -module.exports = { - selectAccountFromConfig, -}; +} diff --git a/lib/prompts/cmsFieldPrompt.ts b/lib/prompts/cmsFieldPrompt.ts index 2d88b244e..adffca040 100644 --- a/lib/prompts/cmsFieldPrompt.ts +++ b/lib/prompts/cmsFieldPrompt.ts @@ -1,13 +1,17 @@ -// @ts-nocheck -const path = require('path'); -const fs = require('fs'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const escapeRegExp = require('@hubspot/local-dev-lib/escapeRegExp'); +import path from 'path'; +import fs from 'fs'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { escapeRegExp } from '@hubspot/local-dev-lib/escapeRegExp'; + const i18nKey = 'lib.prompts.uploadPrompt'; const FIELDS_FILES = ['fields.json', 'fields.js', 'fields.cjs', 'fields.mjs']; -const fieldsJsPrompt = async (filePath, projectDir, skipFiles = []) => { +export async function fieldsJsPrompt( + filePath: string, + projectDir: string, + skipFiles: string[] = [] +): Promise<[string, string[]]> { const dirName = path.dirname(filePath); // Get a list of all field files in the directory, resolve their absolute path, and remove the ones that we already skipped. @@ -18,20 +22,18 @@ const fieldsJsPrompt = async (filePath, projectDir, skipFiles = []) => { .filter(file => !skipFiles.includes(file)); if (!fileChoices.length) return [filePath, []]; - if (fileChoices.length == 1) return [fileChoices[0], []]; + if (fileChoices.length === 1) return [fileChoices[0], []]; // We get the directory above the project one so that relative paths are printed with the root of the project dir attached. projectDir = projectDir.substring(0, projectDir.lastIndexOf('/')); const projectDirRegex = new RegExp(`^${escapeRegExp(projectDir)}`); const fileDir = path.dirname(fileChoices[0]).replace(projectDirRegex, ''); - const selection = []; - fileChoices.forEach(fileChoice => { - selection.push({ - name: fileChoice.replace(projectDirRegex, ''), - value: fileChoice, - }); - }); + const selection = fileChoices.map(fileChoice => ({ + name: fileChoice.replace(projectDirRegex, ''), + value: fileChoice, + })); + const promptVal = await promptUser([ { message: i18n(`${i18nKey}.fieldsPrompt`, { dir: fileDir }), @@ -40,12 +42,11 @@ const fieldsJsPrompt = async (filePath, projectDir, skipFiles = []) => { choices: selection, }, ]); + const choice = promptVal.filePathChoice; // Add the ones that were not picked to skip files array. const notPicked = fileChoices.filter(item => item !== choice); skipFiles.push(...notPicked); return [choice, skipFiles]; -}; - -module.exports = { fieldsJsPrompt }; +} diff --git a/lib/ui/index.ts b/lib/ui/index.ts index 6799b9745..1907e0bac 100644 --- a/lib/ui/index.ts +++ b/lib/ui/index.ts @@ -54,7 +54,10 @@ export function uiLink(linkText: string, url: string): string { : `${linkText}: ${encodedUrl}`; } -export function uiAccountDescription(accountId: number, bold = true): string { +export function uiAccountDescription( + accountId: number | undefined, + bold = true +): string { const account = getAccountConfig(accountId); let message; if (account && account.accountType) { From 53924c885c495cca8cd53289aa06526a46729ba8 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Tue, 3 Dec 2024 16:47:54 -0500 Subject: [PATCH 02/15] Add types to previous changes --- lib/prompts/accountsPrompt.ts | 6 +++--- types/prompts.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/prompts/accountsPrompt.ts b/lib/prompts/accountsPrompt.ts index 21a54515c..c302bdfb6 100644 --- a/lib/prompts/accountsPrompt.ts +++ b/lib/prompts/accountsPrompt.ts @@ -7,10 +7,11 @@ import { promptUser } from './promptUtils'; import { i18n } from '../lang'; import { uiAccountDescription } from '../ui'; import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { PromptChoices } from '../../types/prompts'; function mapAccountChoices( portals: CLIAccount[] | null | undefined -): { name: string | undefined; value: string }[] | [] { +): PromptChoices | [] { return portals ? portals.map(p => ({ name: uiAccountDescription(getAccountIdentifier(p), false), @@ -28,12 +29,11 @@ export async function selectAccountFromConfig(prompt = ''): Promise { const { default: selectedDefault } = await promptUser([ { type: 'list', - look: false, name: 'default', pageSize: 20, message: prompt || i18n(`${i18nKey}.promptMessage`), choices: mapAccountChoices(accountsList), - default: defaultAccount, + default: defaultAccount ?? undefined, }, ]); diff --git a/types/prompts.ts b/types/prompts.ts index 8b7c83c5c..f993e8d76 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -9,7 +9,7 @@ export type PromptChoices = string[] | Array<{ name: string; value: string }>; export type PromptWhen = boolean | (() => boolean); -type PromptOperand = string | boolean | string[] | boolean[]; +type PromptOperand = string | number | boolean | string[] | boolean[]; export type PromptConfig = { name: keyof T; @@ -17,6 +17,7 @@ export type PromptConfig = { message?: string; choices?: PromptChoices; when?: PromptWhen; + pageSize?: number; default?: PromptOperand; validate?: | ((answer?: string) => PromptOperand | Promise) From f5f4c8f228e86676896db61c5be930126a7470aa Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Tue, 3 Dec 2024 20:40:16 -0500 Subject: [PATCH 03/15] Convert prompts to TS p2 --- lang/en.lyaml | 1 + lib/prompts/createApiSamplePrompt.ts | 115 +++++++++++++++++---------- lib/prompts/createProjectPrompt.ts | 88 +++++++++++++------- types/prompts.ts | 18 +++-- 4 files changed, 144 insertions(+), 78 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 7359033b2..1d3ba425d 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1291,6 +1291,7 @@ en: errors: apiSampleAppRequired: "Please select API sample app" languageRequired: "Please select API sample app's language" + sampleNotFound: "A sample app matching your selection could not be found" createProjectPrompt: enterName: "[--name] Give your project a name: " enterDest: "[--dest] Enter the folder to create the project in:" diff --git a/lib/prompts/createApiSamplePrompt.ts b/lib/prompts/createApiSamplePrompt.ts index 7a45a4f43..f7a39b1fc 100644 --- a/lib/prompts/createApiSamplePrompt.ts +++ b/lib/prompts/createApiSamplePrompt.ts @@ -1,52 +1,85 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { PromptOperand, PromptConfig } from '../../types/prompts'; const i18nKey = 'lib.prompts.createApiSamplePrompt'; -const getSampleTypesPrompt = choices => ({ - type: 'rawlist', - name: 'sampleType', - message: i18n(`${i18nKey}.selectApiSampleApp`), - choices: choices.map(choice => ({ - name: `${choice.name} - ${choice.description}`, - value: choice.id, - })), - validate: input => { - return new Promise(function(resolve, reject) { - if (input.length > 0) { - resolve(true); - } - reject(i18n(`${i18nKey}.errors.apiSampleAppRequired`)); - }); - }, -}); +type SampleChoice = { + name: string; + description: string; + id: string; + languages: string[]; +}; + +type SampleConfig = { + samples: SampleChoice[]; +}; + +type createApiSamplePromptResponse = { + sampleType?: string; + sampleLanguage?: string; +}; -const getLanguagesPrompt = choices => ({ - type: 'rawlist', - name: 'sampleLanguage', - message: i18n(`${i18nKey}.selectLanguage`), - choices: choices.map(choice => ({ - name: choice, - value: choice, - })), - validate: input => { - return new Promise(function(resolve, reject) { - if (input.length > 0) { - resolve(true); - } - reject(i18n(`${i18nKey}.errors.languageRequired`)); - }); - }, -}); +function getSampleTypesPrompt( + choices: SampleChoice[] +): PromptConfig { + return { + type: 'rawlist', + name: 'sampleType', + message: i18n(`${i18nKey}.selectApiSampleApp`), + choices: choices.map(choice => ({ + name: `${choice.name} - ${choice.description}`, + value: choice.id, + })), + validate: function(input?: string): Promise { + return new Promise(function(resolve, reject) { + if (input && input.length > 0) { + resolve(true); + } else { + reject(i18n(`${i18nKey}.errors.apiSampleAppRequired`)); + } + }); + }, + }; +} -const createApiSamplePrompt = async samplesConfig => { +function getLanguagesPrompt( + choices: string[] +): PromptConfig { + return { + type: 'rawlist', + name: 'sampleLanguage', + message: i18n(`${i18nKey}.selectLanguage`), + choices: choices.map(choice => ({ + name: choice, + value: choice, + })), + validate: function(input: string | undefined): Promise { + return new Promise(function(resolve, reject) { + if (input && input.length > 0) { + resolve(true); + } + reject(i18n(`${i18nKey}.errors.languageRequired`)); + }); + }, + }; +} + +export async function createApiSamplePrompt( + samplesConfig: SampleConfig +): Promise { try { const { samples } = samplesConfig; const sampleTypeAnswer = await promptUser(getSampleTypesPrompt(samples)); const chosenSample = samples.find( sample => sample.id === sampleTypeAnswer.sampleType ); + if (!chosenSample) { + logger.log(i18n(`${i18nKey}.errors.sampleNotFound`)); + process.exit(EXIT_CODES.ERROR); + } const { languages } = chosenSample; const languagesAnswer = await promptUser(getLanguagesPrompt(languages)); return { @@ -56,8 +89,4 @@ const createApiSamplePrompt = async samplesConfig => { } catch (e) { return {}; } -}; - -module.exports = { - createApiSamplePrompt, -}; +} diff --git a/lib/prompts/createProjectPrompt.ts b/lib/prompts/createProjectPrompt.ts index 298d8f5ba..feeb78b4c 100644 --- a/lib/prompts/createProjectPrompt.ts +++ b/lib/prompts/createProjectPrompt.ts @@ -1,43 +1,63 @@ -// @ts-nocheck -const fs = require('fs'); -const path = require('path'); -const { +import fs from 'fs'; +import path from 'path'; +import { getCwd, sanitizeFileName, isValidPath, untildify, -} = require('@hubspot/local-dev-lib/path'); -const { PROJECT_COMPONENT_TYPES } = require('../constants'); -const { promptUser } = require('./promptUtils'); -const { fetchFileFromRepository } = require('@hubspot/local-dev-lib/github'); -const { i18n } = require('../lang'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { EXIT_CODES } = require('../enums/exitCodes'); -const { +} from '@hubspot/local-dev-lib/path'; +import { + PROJECT_COMPONENT_TYPES, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, -} = require('../constants'); +} from '../constants'; +import { promptUser } from './promptUtils'; +import { fetchFileFromRepository } from '@hubspot/local-dev-lib/github'; +import { i18n } from '../lang'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { RepoPath } from '@hubspot/local-dev-lib/types/Github'; const i18nKey = 'lib.prompts.createProjectPrompt'; const PROJECT_PROPERTIES = ['name', 'label', 'path', 'insertPath']; -const hasAllProperties = projectList => { +type ProjectsConfig = { + projects?: ProjectProperties[]; +}; + +type ProjectProperties = { + name: string; + label: string; + path: string; + insertPath: string; +}; + +type CreateProjectPromptResponse = { + name: string; + dest: string; + template: ProjectProperties; +}; + +function hasAllProperties(projectList: ProjectProperties[]): boolean { return projectList.every(config => PROJECT_PROPERTIES.every(p => Object.prototype.hasOwnProperty.call(config, p) ) ); -}; +} -const createTemplateOptions = async (templateSource, githubRef) => { +async function createTemplateOptions( + templateSource: string, + githubRef: string +): Promise { const hasCustomTemplateSource = Boolean(templateSource); const branch = hasCustomTemplateSource ? DEFAULT_PROJECT_TEMPLATE_BRANCH : githubRef; - const config = await fetchFileFromRepository( - templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + const config: ProjectsConfig = await fetchFileFromRepository( + (templateSource as RepoPath) || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', branch ); @@ -47,26 +67,34 @@ const createTemplateOptions = async (templateSource, githubRef) => { process.exit(EXIT_CODES.ERROR); } - if (!hasAllProperties(config[PROJECT_COMPONENT_TYPES.PROJECTS])) { + if (!hasAllProperties(config[PROJECT_COMPONENT_TYPES.PROJECTS]!)) { logger.error(i18n(`${i18nKey}.errors.missingPropertiesInConfig`)); process.exit(EXIT_CODES.ERROR); } - return config[PROJECT_COMPONENT_TYPES.PROJECTS]; -}; + return config[PROJECT_COMPONENT_TYPES.PROJECTS]!; +} -function findTemplate(projectTemplates, templateNameOrLabel) { +function findTemplate( + projectTemplates: ProjectProperties[], + templateNameOrLabel: string +): ProjectProperties | undefined { return projectTemplates.find( t => t.name === templateNameOrLabel || t.label === templateNameOrLabel ); } -const createProjectPrompt = async ( - githubRef, - promptOptions = {}, +export async function createProjectPrompt( + githubRef: string, + promptOptions: { + name: string; + dest: string; + template: string; + templateSource: string; + }, skipTemplatePrompt = false -) => { - let projectTemplates = []; +): Promise { + let projectTemplates: ProjectProperties[] = []; let selectedTemplate; if (!skipTemplatePrompt) { @@ -85,7 +113,7 @@ const createProjectPrompt = async ( name: 'name', message: i18n(`${i18nKey}.enterName`), when: !promptOptions.name, - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.nameRequired`); } @@ -102,7 +130,7 @@ const createProjectPrompt = async ( ); return path.resolve(getCwd(), projectName); }, - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.destRequired`); } @@ -144,7 +172,7 @@ const createProjectPrompt = async ( } return result; -}; +} module.exports = { createProjectPrompt, diff --git a/types/prompts.ts b/types/prompts.ts index f993e8d76..d73da38f1 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -3,22 +3,30 @@ export type GenericPromptResponse = { [key: string]: any; }; -type PromptType = 'confirm' | 'list' | 'checkbox' | 'input' | 'password'; +type PromptType = + | 'confirm' + | 'list' + | 'checkbox' + | 'input' + | 'password' + | 'rawlist'; -export type PromptChoices = string[] | Array<{ name: string; value: string }>; +export type PromptChoices = + | string[] + | Array<{ name: string; value: string | { [key: string]: string } }>; export type PromptWhen = boolean | (() => boolean); -type PromptOperand = string | number | boolean | string[] | boolean[]; +export type PromptOperand = string | number | boolean | string[] | boolean[]; export type PromptConfig = { name: keyof T; type?: PromptType; - message?: string; + message?: string | ((answers: T) => string); choices?: PromptChoices; when?: PromptWhen; pageSize?: number; - default?: PromptOperand; + default?: PromptOperand | ((answers: T) => PromptOperand); validate?: | ((answer?: string) => PromptOperand | Promise) | ((answer: string[]) => PromptOperand | Promise); From 8a405c48d28d6cb8fee57b105f475f3efd15ab5f Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Wed, 4 Dec 2024 10:33:20 -0500 Subject: [PATCH 04/15] Convert prompts to TS p3 --- lib/prompts/downloadProjectPrompt.ts | 45 ++++++++++++++++----------- lib/prompts/installPublicAppPrompt.ts | 31 +++++++++--------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/prompts/downloadProjectPrompt.ts b/lib/prompts/downloadProjectPrompt.ts index 3b4852f64..07c03865d 100644 --- a/lib/prompts/downloadProjectPrompt.ts +++ b/lib/prompts/downloadProjectPrompt.ts @@ -1,26 +1,37 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { getAccountId } = require('@hubspot/local-dev-lib/config'); -const { fetchProjects } = require('@hubspot/local-dev-lib/api/projects'); -const { logError, ApiErrorContext } = require('../errorHandlers/index'); -const { EXIT_CODES } = require('../enums/exitCodes'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { getAccountId } from '@hubspot/local-dev-lib/config'; +import { fetchProjects } from '@hubspot/local-dev-lib/api/projects'; +import { logError, ApiErrorContext } from '../errorHandlers/index'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { i18n } from '../lang'; +import { Project } from '@hubspot/local-dev-lib/types/Project'; const i18nKey = 'lib.prompts.downloadProjectPrompt'; -const createProjectsList = async accountId => { +type DownloadProjectPromptResponse = { + project: string; +}; + +async function createProjectsList( + accountId: number | null +): Promise { + const account = accountId || undefined; try { - const { data: projects } = await fetchProjects(accountId); + const { data: projects } = await fetchProjects(accountId!); return projects.results; } catch (e) { - logError(e, new ApiErrorContext({ accountId })); + logError(e, new ApiErrorContext({ accountId: account })); process.exit(EXIT_CODES.ERROR); } -}; +} -const downloadProjectPrompt = async (promptOptions = {}) => { - const accountId = getAccountId(promptOptions.account); - const projectsList = await createProjectsList(accountId); +export async function downloadProjectPrompt(promptOptions: { + account?: string; + project?: string; + name?: string; +}): Promise { + const accountId = getAccountId(promptOptions.account) || ''; + const projectsList = accountId ? await createProjectsList(accountId) : []; return promptUser([ { @@ -46,8 +57,4 @@ const downloadProjectPrompt = async (promptOptions = {}) => { }), }, ]); -}; - -module.exports = { - downloadProjectPrompt, -}; +} diff --git a/lib/prompts/installPublicAppPrompt.ts b/lib/prompts/installPublicAppPrompt.ts index 243302ac6..22c56a536 100644 --- a/lib/prompts/installPublicAppPrompt.ts +++ b/lib/prompts/installPublicAppPrompt.ts @@ -1,21 +1,20 @@ -// @ts-nocheck -const open = require('open'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { EXIT_CODES } = require('../enums/exitCodes'); +import open from 'open'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { EXIT_CODES } from '../enums/exitCodes'; const i18nKey = 'lib.prompts.installPublicAppPrompt'; -const installPublicAppPrompt = async ( - env, - targetAccountId, - clientId, - scopes, - redirectUrls, +export async function installPublicAppPrompt( + env: string, + targetAccountId: number, + clientId: number, + scopes: string[], + redirectUrls: string[], isReinstall = false -) => { +): Promise { logger.log(''); if (isReinstall) { logger.log(i18n(`${i18nKey}.reinstallExplanation`)); @@ -47,6 +46,4 @@ const installPublicAppPrompt = async ( `&redirect_uri=${encodeURIComponent(redirectUrls[0])}`; open(url); -}; - -module.exports = { installPublicAppPrompt }; +} From dbfad7214b180917cc226ffaee59f96b198bf5f3 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 5 Dec 2024 12:22:30 -0500 Subject: [PATCH 05/15] Address feedback --- lang/en.lyaml | 1 - lib/prompts/accountsPrompt.ts | 4 ++-- lib/prompts/cmsFieldPrompt.ts | 2 +- lib/prompts/createApiSamplePrompt.ts | 28 +++++++++++++-------------- lib/prompts/createProjectPrompt.ts | 12 ++++-------- lib/prompts/downloadProjectPrompt.ts | 22 +++++++++++++-------- lib/prompts/installPublicAppPrompt.ts | 4 +++- lib/ui/index.ts | 5 +---- 8 files changed, 38 insertions(+), 40 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 1d3ba425d..7359033b2 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1291,7 +1291,6 @@ en: errors: apiSampleAppRequired: "Please select API sample app" languageRequired: "Please select API sample app's language" - sampleNotFound: "A sample app matching your selection could not be found" createProjectPrompt: enterName: "[--name] Give your project a name: " enterDest: "[--dest] Enter the folder to create the project in:" diff --git a/lib/prompts/accountsPrompt.ts b/lib/prompts/accountsPrompt.ts index c302bdfb6..3f2038680 100644 --- a/lib/prompts/accountsPrompt.ts +++ b/lib/prompts/accountsPrompt.ts @@ -11,7 +11,7 @@ import { PromptChoices } from '../../types/prompts'; function mapAccountChoices( portals: CLIAccount[] | null | undefined -): PromptChoices | [] { +): PromptChoices { return portals ? portals.map(p => ({ name: uiAccountDescription(getAccountIdentifier(p), false), @@ -26,7 +26,7 @@ export async function selectAccountFromConfig(prompt = ''): Promise { const accountsList = getConfigAccounts(); const defaultAccount = getConfigDefaultAccount(); - const { default: selectedDefault } = await promptUser([ + const { default: selectedDefault } = await promptUser<{ default: string }>([ { type: 'list', name: 'default', diff --git a/lib/prompts/cmsFieldPrompt.ts b/lib/prompts/cmsFieldPrompt.ts index adffca040..3f6ecbde4 100644 --- a/lib/prompts/cmsFieldPrompt.ts +++ b/lib/prompts/cmsFieldPrompt.ts @@ -34,7 +34,7 @@ export async function fieldsJsPrompt( value: fileChoice, })); - const promptVal = await promptUser([ + const promptVal = await promptUser<{ filePathChoice: string }>([ { message: i18n(`${i18nKey}.fieldsPrompt`, { dir: fileDir }), type: 'list', diff --git a/lib/prompts/createApiSamplePrompt.ts b/lib/prompts/createApiSamplePrompt.ts index f7a39b1fc..9cb881169 100644 --- a/lib/prompts/createApiSamplePrompt.ts +++ b/lib/prompts/createApiSamplePrompt.ts @@ -1,7 +1,5 @@ import { promptUser } from './promptUtils'; import { i18n } from '../lang'; -import { logger } from '@hubspot/local-dev-lib/logger'; -import { EXIT_CODES } from '../enums/exitCodes'; import { PromptOperand, PromptConfig } from '../../types/prompts'; const i18nKey = 'lib.prompts.createApiSamplePrompt'; @@ -17,14 +15,14 @@ type SampleConfig = { samples: SampleChoice[]; }; -type createApiSamplePromptResponse = { +type CreateApiSamplePromptResponse = { sampleType?: string; sampleLanguage?: string; }; function getSampleTypesPrompt( choices: SampleChoice[] -): PromptConfig { +): PromptConfig { return { type: 'rawlist', name: 'sampleType', @@ -33,7 +31,7 @@ function getSampleTypesPrompt( name: `${choice.name} - ${choice.description}`, value: choice.id, })), - validate: function(input?: string): Promise { + validate: function(input?: string) { return new Promise(function(resolve, reject) { if (input && input.length > 0) { resolve(true); @@ -47,7 +45,7 @@ function getSampleTypesPrompt( function getLanguagesPrompt( choices: string[] -): PromptConfig { +): PromptConfig { return { type: 'rawlist', name: 'sampleLanguage', @@ -56,7 +54,7 @@ function getLanguagesPrompt( name: choice, value: choice, })), - validate: function(input: string | undefined): Promise { + validate: function(input: string | undefined) { return new Promise(function(resolve, reject) { if (input && input.length > 0) { resolve(true); @@ -69,19 +67,19 @@ function getLanguagesPrompt( export async function createApiSamplePrompt( samplesConfig: SampleConfig -): Promise { +): Promise { try { const { samples } = samplesConfig; - const sampleTypeAnswer = await promptUser(getSampleTypesPrompt(samples)); + const sampleTypeAnswer = await promptUser( + getSampleTypesPrompt(samples) + ); const chosenSample = samples.find( sample => sample.id === sampleTypeAnswer.sampleType ); - if (!chosenSample) { - logger.log(i18n(`${i18nKey}.errors.sampleNotFound`)); - process.exit(EXIT_CODES.ERROR); - } - const { languages } = chosenSample; - const languagesAnswer = await promptUser(getLanguagesPrompt(languages)); + const { languages } = chosenSample!; + const languagesAnswer = await promptUser( + getLanguagesPrompt(languages) + ); return { ...sampleTypeAnswer, ...languagesAnswer, diff --git a/lib/prompts/createProjectPrompt.ts b/lib/prompts/createProjectPrompt.ts index feeb78b4c..cb279d92d 100644 --- a/lib/prompts/createProjectPrompt.ts +++ b/lib/prompts/createProjectPrompt.ts @@ -48,7 +48,7 @@ function hasAllProperties(projectList: ProjectProperties[]): boolean { } async function createTemplateOptions( - templateSource: string, + templateSource: RepoPath, githubRef: string ): Promise { const hasCustomTemplateSource = Boolean(templateSource); @@ -57,7 +57,7 @@ async function createTemplateOptions( : githubRef; const config: ProjectsConfig = await fetchFileFromRepository( - (templateSource as RepoPath) || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', branch ); @@ -90,7 +90,7 @@ export async function createProjectPrompt( name: string; dest: string; template: string; - templateSource: string; + templateSource: RepoPath; }, skipTemplatePrompt = false ): Promise { @@ -108,7 +108,7 @@ export async function createProjectPrompt( findTemplate(projectTemplates, promptOptions.template); } - const result = await promptUser([ + const result = await promptUser([ { name: 'name', message: i18n(`${i18nKey}.enterName`), @@ -173,7 +173,3 @@ export async function createProjectPrompt( return result; } - -module.exports = { - createProjectPrompt, -}; diff --git a/lib/prompts/downloadProjectPrompt.ts b/lib/prompts/downloadProjectPrompt.ts index 07c03865d..4085a1bf6 100644 --- a/lib/prompts/downloadProjectPrompt.ts +++ b/lib/prompts/downloadProjectPrompt.ts @@ -15,12 +15,18 @@ type DownloadProjectPromptResponse = { async function createProjectsList( accountId: number | null ): Promise { - const account = accountId || undefined; try { - const { data: projects } = await fetchProjects(accountId!); - return projects.results; + if (accountId) { + const { data: projects } = await fetchProjects(accountId); + return projects.results; + } + return []; } catch (e) { - logError(e, new ApiErrorContext({ accountId: account })); + if (accountId) { + logError(e, new ApiErrorContext({ accountId })); + } else { + logError(e); + } process.exit(EXIT_CODES.ERROR); } } @@ -30,10 +36,10 @@ export async function downloadProjectPrompt(promptOptions: { project?: string; name?: string; }): Promise { - const accountId = getAccountId(promptOptions.account) || ''; - const projectsList = accountId ? await createProjectsList(accountId) : []; + const accountId = getAccountId(promptOptions.account); + const projectsList = await createProjectsList(accountId); - return promptUser([ + return promptUser([ { name: 'project', message: () => { @@ -41,7 +47,7 @@ export async function downloadProjectPrompt(promptOptions: { !projectsList.find(p => p.name === promptOptions.name) ? i18n(`${i18nKey}.errors.projectNotFound`, { projectName: promptOptions.project, - accountId, + accountId: accountId || '', }) : i18n(`${i18nKey}.selectProject`); }, diff --git a/lib/prompts/installPublicAppPrompt.ts b/lib/prompts/installPublicAppPrompt.ts index 22c56a536..56f6759af 100644 --- a/lib/prompts/installPublicAppPrompt.ts +++ b/lib/prompts/installPublicAppPrompt.ts @@ -22,7 +22,9 @@ export async function installPublicAppPrompt( logger.log(i18n(`${i18nKey}.explanation`)); } - const { shouldOpenBrowser } = await promptUser({ + const { shouldOpenBrowser } = await promptUser<{ + shouldOpenBrowser: boolean; + }>({ name: 'shouldOpenBrowser', type: 'confirm', message: i18n( diff --git a/lib/ui/index.ts b/lib/ui/index.ts index 1907e0bac..edbeac7e7 100644 --- a/lib/ui/index.ts +++ b/lib/ui/index.ts @@ -54,10 +54,7 @@ export function uiLink(linkText: string, url: string): string { : `${linkText}: ${encodedUrl}`; } -export function uiAccountDescription( - accountId: number | undefined, - bold = true -): string { +export function uiAccountDescription(accountId?: number, bold = true): string { const account = getAccountConfig(accountId); let message; if (account && account.accountType) { From b489981de0f6aee7d06c2dbdf05b03e7c7bef174 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 5 Dec 2024 12:28:55 -0500 Subject: [PATCH 06/15] Address feedback p2 --- lib/prompts/accountsPrompt.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/prompts/accountsPrompt.ts b/lib/prompts/accountsPrompt.ts index 3f2038680..58f4c8e26 100644 --- a/lib/prompts/accountsPrompt.ts +++ b/lib/prompts/accountsPrompt.ts @@ -12,12 +12,12 @@ import { PromptChoices } from '../../types/prompts'; function mapAccountChoices( portals: CLIAccount[] | null | undefined ): PromptChoices { - return portals - ? portals.map(p => ({ - name: uiAccountDescription(getAccountIdentifier(p), false), - value: String(p.name || getAccountIdentifier(p)), - })) - : []; + return ( + portals?.map(p => ({ + name: uiAccountDescription(getAccountIdentifier(p), false), + value: String(p.name || getAccountIdentifier(p)), + })) || [] + ); } const i18nKey = 'commands.account.subcommands.use'; From d8d7cdbe0888380db1278cb75d3fc9530701d9b8 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Wed, 4 Dec 2024 14:20:42 -0500 Subject: [PATCH 07/15] Convert prompts to TS p1 --- lib/prompts/accountNamePrompt.ts | 2 +- lib/prompts/personalAccessKeyPrompt.ts | 101 +++++++++++++++---------- lib/prompts/previewPrompt.ts | 39 ++++++---- lib/prompts/projectAddPrompt.ts | 29 ++++--- types/prompts.ts | 3 + 5 files changed, 107 insertions(+), 67 deletions(-) diff --git a/lib/prompts/accountNamePrompt.ts b/lib/prompts/accountNamePrompt.ts index dfebd164d..4efaf6c59 100644 --- a/lib/prompts/accountNamePrompt.ts +++ b/lib/prompts/accountNamePrompt.ts @@ -12,7 +12,7 @@ type AccountNamePromptResponse = { }; export function getCliAccountNamePromptConfig( - defaultName: string + defaultName?: string ): PromptConfig { return { name: 'name', diff --git a/lib/prompts/personalAccessKeyPrompt.ts b/lib/prompts/personalAccessKeyPrompt.ts index ed835325a..d23c5f3e3 100644 --- a/lib/prompts/personalAccessKeyPrompt.ts +++ b/lib/prompts/personalAccessKeyPrompt.ts @@ -1,25 +1,56 @@ -// @ts-nocheck -const open = require('open'); -const { +import open from 'open'; +import { OAUTH_SCOPES, DEFAULT_OAUTH_SCOPES, -} = require('@hubspot/local-dev-lib/constants/auth'); -const { deleteEmptyConfigFile } = require('@hubspot/local-dev-lib/config'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { promptUser } = require('./promptUtils'); -const { getCliAccountNamePromptConfig } = require('./accountNamePrompt'); -const { i18n } = require('../lang'); -const { uiInfoSection } = require('../ui'); -const { EXIT_CODES } = require('../enums/exitCodes'); +} from '@hubspot/local-dev-lib/constants/auth'; +import { deleteEmptyConfigFile } from '@hubspot/local-dev-lib/config'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { promptUser } from './promptUtils'; +import { getCliAccountNamePromptConfig } from './accountNamePrompt'; +import { i18n } from '../lang'; +import { uiInfoSection } from '../ui'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { PromptConfig } from '../../types/prompts'; const i18nKey = 'lib.prompts.personalAccessKeyPrompt'; +type PersonalAccessKeyPromptResponse = { + personalAccessKey: string; + env: string; +}; + +type AccountIdPromptResponse = { + accountId: number; +}; + +type ClientIdPromptResponse = { + clientId: string; +}; + +type ClientSecretPromptResponse = { + clientSecret: string; +}; + +type PersonalAcessKeyBrowserOpenPrepResponse = { + personalAcessKeyBrowserOpenPrep: boolean; +}; + +type ScopesPromptResponse = { + scopes: string; +}; + /** * Displays notification to user that we are about to open the browser, * then opens their browser to the personal-access-key shortlink */ -const personalAccessKeyPrompt = async ({ env, account } = {}) => { +export async function personalAccessKeyPrompt({ + env, + account, +}: { + env: string; + account?: string; +}): Promise { const websiteOrigin = getHubSpotWebsiteOrigin(env); let url = `${websiteOrigin}/l/personal-access-key`; if (process.env.BROWSER !== 'none') { @@ -47,24 +78,24 @@ const personalAccessKeyPrompt = async ({ env, account } = {}) => { personalAccessKey, env, }; -}; +} -const ACCOUNT_ID = { +export const ACCOUNT_ID: PromptConfig = { name: 'accountId', message: i18n(`${i18nKey}.enterAccountId`), type: 'number', - validate(val) { - if (!Number.isNaN(val) && val > 0) { + validate(val?: number) { + if (!Number.isNaN(val) && val !== undefined && val > 0) { return true; } return i18n(`${i18nKey}.errors.invalidAccountId`); }, }; -const CLIENT_ID = { +export const CLIENT_ID: PromptConfig = { name: 'clientId', message: i18n(`${i18nKey}.enterClientId`), - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalidOauthClientId`); } else if (val.length !== 36) { @@ -74,10 +105,10 @@ const CLIENT_ID = { }, }; -const CLIENT_SECRET = { +export const CLIENT_SECRET: PromptConfig = { name: 'clientSecret', message: i18n(`${i18nKey}.enterClientSecret`), - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalidOauthClientSecret`); } else if (val.length !== 36) { @@ -89,16 +120,16 @@ const CLIENT_SECRET = { }, }; -const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP = { +export const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = { name: 'personalAcessKeyBrowserOpenPrep', type: 'confirm', message: i18n(`${i18nKey}.personalAccessKeyBrowserOpenPrompt`), }; -const PERSONAL_ACCESS_KEY = { +export const PERSONAL_ACCESS_KEY: PromptConfig = { name: 'personalAccessKey', message: i18n(`${i18nKey}.enterPersonalAccessKey`), - transformer: val => { + transformer: (val?: string) => { if (!val) return val; let res = ''; for (let i = 0; i < val.length; i++) { @@ -106,7 +137,7 @@ const PERSONAL_ACCESS_KEY = { } return res; }, - validate(val) { + validate(val?: string) { if (!val || typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalidPersonalAccessKey`); } else if (val[0] === '•') { @@ -116,29 +147,19 @@ const PERSONAL_ACCESS_KEY = { }, }; -const SCOPES = { +export const SCOPES: PromptConfig = { type: 'checkbox', name: 'scopes', message: i18n(`${i18nKey}.selectScopes`), - default: DEFAULT_OAUTH_SCOPES, - choices: OAUTH_SCOPES, + default: [...DEFAULT_OAUTH_SCOPES], + choices: [...OAUTH_SCOPES], }; -const OAUTH_FLOW = [ +// TODO: Type needed? +export const OAUTH_FLOW = [ getCliAccountNamePromptConfig(), ACCOUNT_ID, CLIENT_ID, CLIENT_SECRET, SCOPES, ]; - -module.exports = { - personalAccessKeyPrompt, - CLIENT_ID, - CLIENT_SECRET, - ACCOUNT_ID, - SCOPES, - PERSONAL_ACCESS_KEY, - // Flows - OAUTH_FLOW, -}; diff --git a/lib/prompts/previewPrompt.ts b/lib/prompts/previewPrompt.ts index 6f3ddfc27..28120a63f 100644 --- a/lib/prompts/previewPrompt.ts +++ b/lib/prompts/previewPrompt.ts @@ -1,19 +1,29 @@ -// @ts-nocheck -const path = require('path'); -const { getCwd } = require('@hubspot/local-dev-lib/path'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import path from 'path'; +import { getCwd } from '@hubspot/local-dev-lib/path'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.previewPrompt'; -const previewPrompt = (promptOptions = {}) => { +type PreviewPromptResponse = { + src: string; + dest: string; +}; + +type PreviewProjectPromptResponse = { + themeComponentPath: string; +}; + +export async function previewPrompt( + promptOptions: { src?: string; dest?: string } = {} +): Promise { return promptUser([ { name: 'src', message: i18n(`${i18nKey}.enterSrc`), when: !promptOptions.src, default: '.', - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.srcRequired`); } @@ -25,7 +35,7 @@ const previewPrompt = (promptOptions = {}) => { message: i18n(`${i18nKey}.enterDest`), when: !promptOptions.dest, default: path.basename(getCwd()), - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.destRequired`); } @@ -33,9 +43,11 @@ const previewPrompt = (promptOptions = {}) => { }, }, ]); -}; +} -const previewProjectPrompt = async themeComponents => { +export async function previewProjectPrompt( + themeComponents: { path: string }[] +): Promise { return promptUser([ { name: 'themeComponentPath', @@ -50,9 +62,4 @@ const previewProjectPrompt = async themeComponents => { }), }, ]); -}; - -module.exports = { - previewPrompt, - previewProjectPrompt, -}; +} diff --git a/lib/prompts/projectAddPrompt.ts b/lib/prompts/projectAddPrompt.ts index 836ace139..1d2c66b29 100644 --- a/lib/prompts/projectAddPrompt.ts +++ b/lib/prompts/projectAddPrompt.ts @@ -1,10 +1,23 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.projectAddPrompt'; -const projectAddPrompt = async (components, promptOptions = {}) => { +type Component = { + path: string; + label: string; + insertPath: string; +}; + +type ProjectAddPromptResponse = { + component: Component; + name: string; +}; + +export async function projectAddPrompt( + components: Component[], + promptOptions: { name?: string; type?: string } = {} +): Promise { return promptUser([ { name: 'component', @@ -31,7 +44,7 @@ const projectAddPrompt = async (components, promptOptions = {}) => { name: 'name', message: i18n(`${i18nKey}.enterName`), when: !promptOptions.name, - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.nameRequired`); } @@ -39,8 +52,4 @@ const projectAddPrompt = async (components, promptOptions = {}) => { }, }, ]); -}; - -module.exports = { - projectAddPrompt, -}; +} diff --git a/types/prompts.ts b/types/prompts.ts index d73da38f1..f67154fd9 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -9,6 +9,7 @@ type PromptType = | 'checkbox' | 'input' | 'password' + | 'number' | 'rawlist'; export type PromptChoices = @@ -27,8 +28,10 @@ export type PromptConfig = { when?: PromptWhen; pageSize?: number; default?: PromptOperand | ((answers: T) => PromptOperand); + transformer?: (input: string) => string | undefined; validate?: | ((answer?: string) => PromptOperand | Promise) + | ((answer?: number) => PromptOperand | Promise) | ((answer: string[]) => PromptOperand | Promise); mask?: string; filter?: (input: string) => string; From 8ad2780e3bf3894783af171b716dd2016f453155 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Wed, 4 Dec 2024 16:15:41 -0500 Subject: [PATCH 08/15] Convert prompts to TS p2 --- lib/prompts/createTemplatePrompt.ts | 2 +- lib/prompts/projectDevTargetAccountPrompt.ts | 149 +++++++++++-------- types/prompts.ts | 22 ++- 3 files changed, 103 insertions(+), 70 deletions(-) diff --git a/lib/prompts/createTemplatePrompt.ts b/lib/prompts/createTemplatePrompt.ts index 374113549..9a8b0e524 100644 --- a/lib/prompts/createTemplatePrompt.ts +++ b/lib/prompts/createTemplatePrompt.ts @@ -15,7 +15,7 @@ const templateTypeChoices: PromptChoices = [ ] as const; interface CreateTemplatePromptResponse { - templateType: typeof templateTypeChoices[number]['value']; + templateType: typeof templateTypeChoices[number]; } const TEMPLATE_TYPE_PROMPT: PromptConfig = { diff --git a/lib/prompts/projectDevTargetAccountPrompt.ts b/lib/prompts/projectDevTargetAccountPrompt.ts index d1978eaa9..0e3d8dfc4 100644 --- a/lib/prompts/projectDevTargetAccountPrompt.ts +++ b/lib/prompts/projectDevTargetAccountPrompt.ts @@ -1,51 +1,67 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { uiAccountDescription, uiCommandReference } = require('../ui'); -const { isSandbox } = require('../accountTypes'); -const { getAccountId } = require('@hubspot/local-dev-lib/config'); -const { - getSandboxUsageLimits, -} = require('@hubspot/local-dev-lib/api/sandboxHubs'); -const { +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { uiAccountDescription, uiCommandReference } from '../ui'; +import { isSandbox } from '../accountTypes'; +import { getAccountId } from '@hubspot/local-dev-lib/config'; +import { getSandboxUsageLimits } from '@hubspot/local-dev-lib/api/sandboxHubs'; +import { HUBSPOT_ACCOUNT_TYPES, HUBSPOT_ACCOUNT_TYPE_STRINGS, -} = require('@hubspot/local-dev-lib/constants/config'); -const { - getAccountIdentifier, -} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - fetchDeveloperTestAccounts, -} = require('@hubspot/local-dev-lib/api/developerTestAccounts'); +} from '@hubspot/local-dev-lib/constants/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts'; +import { CLIAccount, AccountType } from '@hubspot/local-dev-lib/types/Accounts'; +import { Usage } from '@hubspot/local-dev-lib/types/Sandbox'; +import { DeveloperTestAccount } from '@hubspot/local-dev-lib/types/DeveloperTestAccounts'; +import { PromptChoices } from '../../types/prompts'; const i18nKey = 'lib.prompts.projectDevTargetAccountPrompt'; -const mapNestedAccount = accountConfig => ({ - name: uiAccountDescription(getAccountIdentifier(accountConfig), false), +function mapNestedAccount( + accountConfig: CLIAccount +): { + name: string; value: { - targetAccountId: getAccountId(accountConfig.name), - createNestedAccount: false, - parentAccountId: accountConfig.parentAccountId, - }, -}); + targetAccountId: number | null; + createNestedAccount: boolean; + parentAccountId: number | null; + }; +} { + const parentAccountId = accountConfig.parentAccountId ?? null; + return { + name: uiAccountDescription(getAccountIdentifier(accountConfig), false), + value: { + targetAccountId: getAccountId(accountConfig.name), + createNestedAccount: false, + parentAccountId, + }, + }; +} -const getNonConfigDeveloperTestAccountName = account => { +function getNonConfigDeveloperTestAccountName( + account: DeveloperTestAccount +): string { return `${account.accountName} [${ HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST] }] (${account.id})`; -}; +} -const selectSandboxTargetAccountPrompt = async ( - accounts, - defaultAccountConfig -) => { +export async function selectSandboxTargetAccountPrompt( + accounts: CLIAccount[], + defaultAccountConfig: CLIAccount +): Promise { const defaultAccountId = getAccountId(defaultAccountConfig.name); let choices = []; - let sandboxUsage = {}; + let sandboxUsage: Usage = { + STANDARD: { used: 0, available: 0, limit: 0 }, + DEVELOPER: { used: 0, available: 0, limit: 0 }, + }; try { - const { data } = await getSandboxUsageLimits(defaultAccountId); - sandboxUsage = data.usage; + if (defaultAccountId) { + const { data } = await getSandboxUsageLimits(defaultAccountId); + sandboxUsage = data.usage; + } } catch (err) { logger.debug('Unable to fetch sandbox usage limits: ', err); } @@ -55,7 +71,7 @@ const selectSandboxTargetAccountPrompt = async ( .filter( config => isSandbox(config) && config.parentAccountId === defaultAccountId ); - let disabledMessage = false; + let disabledMessage: string | boolean = false; if (sandboxUsage['DEVELOPER'] && sandboxUsage['DEVELOPER'].available === 0) { if (sandboxAccounts.length < sandboxUsage['DEVELOPER'].limit) { @@ -99,22 +115,24 @@ const selectSandboxTargetAccountPrompt = async ( 'sandbox account', choices ); -}; +} -const selectDeveloperTestTargetAccountPrompt = async ( - accounts, - defaultAccountConfig -) => { +export async function selectDeveloperTestTargetAccountPrompt( + accounts: CLIAccount[], + defaultAccountConfig: CLIAccount +): Promise { const defaultAccountId = getAccountId(defaultAccountConfig.name); let devTestAccountsResponse = undefined; try { - const { data } = await fetchDeveloperTestAccounts(defaultAccountId); - devTestAccountsResponse = data; + if (defaultAccountId) { + const { data } = await fetchDeveloperTestAccounts(defaultAccountId); + devTestAccountsResponse = data; + } } catch (err) { logger.debug('Unable to fetch developer test account usage limits: ', err); } - let disabledMessage = false; + let disabledMessage: string | boolean = false; if ( devTestAccountsResponse && devTestAccountsResponse.results.length >= @@ -126,7 +144,7 @@ const selectDeveloperTestTargetAccountPrompt = async ( }); } - const devTestAccounts = []; + const devTestAccounts: PromptChoices = []; if (devTestAccountsResponse && devTestAccountsResponse.results) { const accountIds = accounts.map(account => getAccountIdentifier(account)); @@ -136,8 +154,8 @@ const selectDeveloperTestTargetAccountPrompt = async ( name: getNonConfigDeveloperTestAccountName(acct), value: { targetAccountId: acct.id, - createdNestedAccount: false, - parentAccountId: defaultAccountId, + createNestedAccount: false, + parentAccountId: defaultAccountId ?? null, notInConfigAccount: inConfig ? null : acct, }, }); @@ -161,19 +179,20 @@ const selectDeveloperTestTargetAccountPrompt = async ( HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST], choices ); -}; - -const selectTargetAccountPrompt = async ( - defaultAccountId, - accountType, - choices -) => { +} + +async function selectTargetAccountPrompt( + defaultAccountId: number | null, + accountType: string, + choices: PromptChoices +): Promise { + const accountId = defaultAccountId || undefined; const { targetAccountInfo } = await promptUser([ { name: 'targetAccountInfo', type: 'list', message: i18n(`${i18nKey}.promptMessage`, { - accountIdentifier: uiAccountDescription(defaultAccountId), + accountIdentifier: uiAccountDescription(accountId), accountType, }), choices, @@ -181,9 +200,12 @@ const selectTargetAccountPrompt = async ( ]); return targetAccountInfo; -}; +} -const confirmDefaultAccountPrompt = async (accountName, accountType) => { +export async function confirmDefaultAccountPrompt( + accountName: string, + accountType: AccountType +): Promise { const { useDefaultAccount } = await promptUser([ { name: 'useDefaultAccount', @@ -195,9 +217,11 @@ const confirmDefaultAccountPrompt = async (accountName, accountType) => { }, ]); return useDefaultAccount; -}; +} -const confirmUseExistingDeveloperTestAccountPrompt = async account => { +export async function confirmUseExistingDeveloperTestAccountPrompt( + account: DeveloperTestAccount +): Promise { const { confirmUseExistingDeveloperTestAccount } = await promptUser([ { name: 'confirmUseExistingDeveloperTestAccount', @@ -208,11 +232,4 @@ const confirmUseExistingDeveloperTestAccountPrompt = async account => { }, ]); return confirmUseExistingDeveloperTestAccount; -}; - -module.exports = { - selectSandboxTargetAccountPrompt, - selectDeveloperTestTargetAccountPrompt, - confirmDefaultAccountPrompt, - confirmUseExistingDeveloperTestAccountPrompt, -}; +} diff --git a/types/prompts.ts b/types/prompts.ts index f67154fd9..cdce9b794 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -1,3 +1,5 @@ +import { DeveloperTestAccount } from '@hubspot/local-dev-lib/types/developerTestAccounts'; + export type GenericPromptResponse = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -12,9 +14,23 @@ type PromptType = | 'number' | 'rawlist'; -export type PromptChoices = - | string[] - | Array<{ name: string; value: string | { [key: string]: string } }>; +export type PromptChoices = Array< + | string + | { + name: string; + value: + | string + | { + [key: string]: + | string + | number + | boolean + | DeveloperTestAccount + | null; + }; + disabled?: string | boolean; + } +>; export type PromptWhen = boolean | (() => boolean); From 0cba60cdc393c9ba8a6b5141b1899970ce3aa6d7 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 5 Dec 2024 14:26:34 -0500 Subject: [PATCH 09/15] Add promptUser return types p1 --- lib/prompts/personalAccessKeyPrompt.ts | 14 ++++++++------ lib/prompts/previewPrompt.ts | 4 ++-- lib/prompts/projectAddPrompt.ts | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/prompts/personalAccessKeyPrompt.ts b/lib/prompts/personalAccessKeyPrompt.ts index d23c5f3e3..c1d1df445 100644 --- a/lib/prompts/personalAccessKeyPrompt.ts +++ b/lib/prompts/personalAccessKeyPrompt.ts @@ -32,7 +32,7 @@ type ClientSecretPromptResponse = { clientSecret: string; }; -type PersonalAcessKeyBrowserOpenPrepResponse = { +type PersonalAccessKeyBrowserOpenPrepResponse = { personalAcessKeyBrowserOpenPrep: boolean; }; @@ -60,9 +60,9 @@ export async function personalAccessKeyPrompt({ if (account) { url = `${websiteOrigin}/personal-access-key/${account}`; } - const { personalAcessKeyBrowserOpenPrep: shouldOpen } = await promptUser([ - PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP, - ]); + const { personalAcessKeyBrowserOpenPrep: shouldOpen } = await promptUser<{ + personalAcessKeyBrowserOpenPrep: boolean; + }>([PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP]); if (shouldOpen) { open(url, { url: true }); } else { @@ -72,7 +72,9 @@ export async function personalAccessKeyPrompt({ } logger.log(i18n(`${i18nKey}.logs.openingWebBrowser`, { url })); - const { personalAccessKey } = await promptUser(PERSONAL_ACCESS_KEY); + const { personalAccessKey } = await promptUser< + PersonalAccessKeyPromptResponse + >(PERSONAL_ACCESS_KEY); return { personalAccessKey, @@ -120,7 +122,7 @@ export const CLIENT_SECRET: PromptConfig = { }, }; -export const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = { +export const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = { name: 'personalAcessKeyBrowserOpenPrep', type: 'confirm', message: i18n(`${i18nKey}.personalAccessKeyBrowserOpenPrompt`), diff --git a/lib/prompts/previewPrompt.ts b/lib/prompts/previewPrompt.ts index 28120a63f..00981368e 100644 --- a/lib/prompts/previewPrompt.ts +++ b/lib/prompts/previewPrompt.ts @@ -17,7 +17,7 @@ type PreviewProjectPromptResponse = { export async function previewPrompt( promptOptions: { src?: string; dest?: string } = {} ): Promise { - return promptUser([ + return promptUser([ { name: 'src', message: i18n(`${i18nKey}.enterSrc`), @@ -48,7 +48,7 @@ export async function previewPrompt( export async function previewProjectPrompt( themeComponents: { path: string }[] ): Promise { - return promptUser([ + return promptUser([ { name: 'themeComponentPath', message: i18n(`${i18nKey}.themeProjectSelect`), diff --git a/lib/prompts/projectAddPrompt.ts b/lib/prompts/projectAddPrompt.ts index 1d2c66b29..1d682bc3f 100644 --- a/lib/prompts/projectAddPrompt.ts +++ b/lib/prompts/projectAddPrompt.ts @@ -18,7 +18,7 @@ export async function projectAddPrompt( components: Component[], promptOptions: { name?: string; type?: string } = {} ): Promise { - return promptUser([ + return promptUser([ { name: 'component', message: () => { From 061611db0777174a3a2df221637cf15bc509f797 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Thu, 5 Dec 2024 14:49:01 -0500 Subject: [PATCH 10/15] Convert prompts to TS p3 --- lib/prompts/projectDevTargetAccountPrompt.ts | 14 +++- lib/prompts/sandboxesPrompt.ts | 86 ++++++++++---------- lib/prompts/selectPublicAppPrompt.ts | 66 ++++++++------- lib/prompts/uploadPrompt.ts | 30 +++---- types/prompts.ts | 3 +- 5 files changed, 110 insertions(+), 89 deletions(-) diff --git a/lib/prompts/projectDevTargetAccountPrompt.ts b/lib/prompts/projectDevTargetAccountPrompt.ts index 0e3d8dfc4..1fe9a1fbb 100644 --- a/lib/prompts/projectDevTargetAccountPrompt.ts +++ b/lib/prompts/projectDevTargetAccountPrompt.ts @@ -185,9 +185,11 @@ async function selectTargetAccountPrompt( defaultAccountId: number | null, accountType: string, choices: PromptChoices -): Promise { +): Promise { const accountId = defaultAccountId || undefined; - const { targetAccountInfo } = await promptUser([ + const { targetAccountInfo } = await promptUser<{ + targetAccountInfo: CLIAccount | DeveloperTestAccount; + }>([ { name: 'targetAccountInfo', type: 'list', @@ -206,7 +208,9 @@ export async function confirmDefaultAccountPrompt( accountName: string, accountType: AccountType ): Promise { - const { useDefaultAccount } = await promptUser([ + const { useDefaultAccount } = await promptUser<{ + useDefaultAccount: boolean; + }>([ { name: 'useDefaultAccount', type: 'confirm', @@ -222,7 +226,9 @@ export async function confirmDefaultAccountPrompt( export async function confirmUseExistingDeveloperTestAccountPrompt( account: DeveloperTestAccount ): Promise { - const { confirmUseExistingDeveloperTestAccount } = await promptUser([ + const { confirmUseExistingDeveloperTestAccount } = await promptUser<{ + confirmUseExistingDeveloperTestAccount: boolean; + }>([ { name: 'confirmUseExistingDeveloperTestAccount', type: 'confirm', diff --git a/lib/prompts/sandboxesPrompt.ts b/lib/prompts/sandboxesPrompt.ts index a460aa074..fbd6c760c 100644 --- a/lib/prompts/sandboxesPrompt.ts +++ b/lib/prompts/sandboxesPrompt.ts @@ -1,48 +1,54 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { uiAccountDescription } = require('../ui'); -const { - HUBSPOT_ACCOUNT_TYPES, -} = require('@hubspot/local-dev-lib/constants/config'); -const { - getAccountIdentifier, -} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); -const { isSandbox } = require('../accountTypes'); -const { +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { uiAccountDescription } from '../ui'; +import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { isSandbox } from '../accountTypes'; +import { getConfigDefaultAccount, getConfigAccounts, -} = require('@hubspot/local-dev-lib/config'); +} from '@hubspot/local-dev-lib/config'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { PromptChoices, GenericPromptResponse } from '../../types/prompts'; const i18nKey = 'lib.prompts.sandboxesPrompt'; -const mapSandboxAccountChoices = portals => - portals - .filter(p => isSandbox(p)) - .map(p => { - return { +type SandboxTypePromptResponse = { + type: string; +}; + +function mapSandboxAccountChoices( + portals: CLIAccount[] | null | undefined +): PromptChoices { + return ( + portals + ?.filter(p => !isSandbox(p)) + .map(p => ({ name: uiAccountDescription(getAccountIdentifier(p), false), value: p.name || getAccountIdentifier(p), - }; - }); + })) || [] + ); +} -const mapNonSandboxAccountChoices = portals => - portals - .filter(p => !isSandbox(p)) - .map(p => { - return { +function mapNonSandboxAccountChoices( + portals: CLIAccount[] | null | undefined +): PromptChoices { + return ( + portals + ?.filter(p => !isSandbox(p)) + .map(p => ({ name: `${p.name} (${getAccountIdentifier(p)})`, value: p.name || getAccountIdentifier(p), - }; - }); + })) || [] + ); +} -const sandboxTypePrompt = () => { - return promptUser([ +export async function sandboxTypePrompt(): Promise { + return promptUser([ { name: 'type', message: i18n(`${i18nKey}.type.message`), type: 'list', - look: false, choices: [ { name: i18n(`${i18nKey}.type.developer`), @@ -56,17 +62,19 @@ const sandboxTypePrompt = () => { default: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, }, ]); -}; +} -const deleteSandboxPrompt = (promptParentAccount = false) => { +export function deleteSandboxPrompt( + promptParentAccount = false +): Promise { const accountsList = getConfigAccounts(); const choices = promptParentAccount ? mapNonSandboxAccountChoices(accountsList) : mapSandboxAccountChoices(accountsList); if (!choices.length) { - return undefined; + return Promise.resolve(undefined); } - return promptUser([ + return promptUser([ { name: 'account', message: i18n( @@ -75,15 +83,9 @@ const deleteSandboxPrompt = (promptParentAccount = false) => { : `${i18nKey}.selectAccountName` ), type: 'list', - look: false, pageSize: 20, choices, - default: getConfigDefaultAccount(), + default: getConfigDefaultAccount() || undefined, }, ]); -}; - -module.exports = { - sandboxTypePrompt, - deleteSandboxPrompt, -}; +} diff --git a/lib/prompts/selectPublicAppPrompt.ts b/lib/prompts/selectPublicAppPrompt.ts index 2f977ac12..ecc028900 100644 --- a/lib/prompts/selectPublicAppPrompt.ts +++ b/lib/prompts/selectPublicAppPrompt.ts @@ -1,25 +1,31 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { uiLine } = require('../ui'); -const { logError } = require('../errorHandlers/index'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - fetchPublicAppsForPortal, -} = require('@hubspot/local-dev-lib/api/appsDev'); -const { EXIT_CODES } = require('../enums/exitCodes'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { uiLine } from '../ui'; +import { logError } from '../errorHandlers/index'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { fetchPublicAppsForPortal } from '@hubspot/local-dev-lib/api/appsDev'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { PublicApp } from '@hubspot/local-dev-lib/types/Apps'; const i18nKey = 'lib.prompts.selectPublicAppPrompt'; -const fetchPublicAppOptions = async ( - accountId, - accountName, +type PublicAppPromptResponse = { + appId: number; +}; + +async function fetchPublicAppOptions( + accountId: number | null, + accountName: string, isMigratingApp = false -) => { +): Promise { try { - const { - data: { results: publicApps }, - } = await fetchPublicAppsForPortal(accountId); + let publicApps: PublicApp[] = []; + if (accountId) { + const { + data: { results: apps }, + } = await fetchPublicAppsForPortal(accountId); + publicApps = apps; + } const filteredPublicApps = publicApps.filter( app => !app.projectId && !app.sourceId ); @@ -47,18 +53,26 @@ const fetchPublicAppOptions = async ( } return filteredPublicApps; } catch (error) { - logError(error, { accountId }); + if (accountId) { + logError(error, { accountId }); + } else { + logError(error); + } logger.error(i18n(`${i18nKey}.errors.errorFetchingApps`)); process.exit(EXIT_CODES.ERROR); } -}; +} -const selectPublicAppPrompt = async ({ +export async function selectPublicAppPrompt({ accountId, accountName, isMigratingApp = false, -}) => { - const publicApps = await fetchPublicAppOptions( +}: { + accountId: number | null; + accountName: string; + isMigratingApp?: boolean; +}): Promise { + const publicApps: PublicApp[] = await fetchPublicAppOptions( accountId, accountName, isMigratingApp @@ -67,7 +81,7 @@ const selectPublicAppPrompt = async ({ ? 'selectAppIdMigrate' : 'selectAppIdClone'; - return promptUser([ + return promptUser([ { name: 'appId', message: i18n(`${i18nKey}.${translationKey}`, { @@ -89,8 +103,4 @@ const selectPublicAppPrompt = async ({ }), }, ]); -}; - -module.exports = { - selectPublicAppPrompt, -}; +} diff --git a/lib/prompts/uploadPrompt.ts b/lib/prompts/uploadPrompt.ts index 886d6fd54..9d021d2fc 100644 --- a/lib/prompts/uploadPrompt.ts +++ b/lib/prompts/uploadPrompt.ts @@ -1,19 +1,25 @@ -// @ts-nocheck -const path = require('path'); -const { getCwd } = require('@hubspot/local-dev-lib/path'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import path from 'path'; +import { getCwd } from '@hubspot/local-dev-lib/path'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.uploadPrompt'; -const uploadPrompt = (promptOptions = {}) => { - return promptUser([ +type UploadPromptResponse = { + src: string; + dest: string; +}; + +export async function uploadPrompt( + promptOptions: { src?: string; dest?: string } = {} +): Promise { + return promptUser([ { name: 'src', message: i18n(`${i18nKey}.enterSrc`), when: !promptOptions.src, default: '.', - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.srcRequired`); } @@ -25,7 +31,7 @@ const uploadPrompt = (promptOptions = {}) => { message: i18n(`${i18nKey}.enterDest`), when: !promptOptions.dest, default: path.basename(getCwd()), - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.destRequired`); } @@ -33,8 +39,4 @@ const uploadPrompt = (promptOptions = {}) => { }, }, ]); -}; - -module.exports = { - uploadPrompt, -}; +} diff --git a/types/prompts.ts b/types/prompts.ts index cdce9b794..9e6b3e495 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -18,8 +18,9 @@ export type PromptChoices = Array< | string | { name: string; - value: + value?: | string + | number | { [key: string]: | string From d73f2185f196a582671543443e00908845022b7c Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 6 Dec 2024 12:43:02 -0500 Subject: [PATCH 11/15] Address feedback 12-6 --- lang/en.lyaml | 1 + lib/prompts/createApiSamplePrompt.ts | 22 ++++++++++++++-------- lib/prompts/downloadProjectPrompt.ts | 4 +++- types/prompts.ts | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 7359033b2..0da24cc7f 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1317,6 +1317,7 @@ en: selectProject: "Select a project to download:" errors: projectNotFound: "Your project {{ projectName }} could not be found in {{ accountId }}. Please select a valid project:" + accountIdRequired: "An account ID is required to download a project." projectAddPrompt: selectType: "[--type] Select your component type:" enterName: "[--name] Give your component a name: " diff --git a/lib/prompts/createApiSamplePrompt.ts b/lib/prompts/createApiSamplePrompt.ts index 9cb881169..d9ab223ba 100644 --- a/lib/prompts/createApiSamplePrompt.ts +++ b/lib/prompts/createApiSamplePrompt.ts @@ -1,6 +1,6 @@ import { promptUser } from './promptUtils'; import { i18n } from '../lang'; -import { PromptOperand, PromptConfig } from '../../types/prompts'; +import { PromptConfig } from '../../types/prompts'; const i18nKey = 'lib.prompts.createApiSamplePrompt'; @@ -15,14 +15,20 @@ type SampleConfig = { samples: SampleChoice[]; }; -type CreateApiSamplePromptResponse = { +type SampleTypePromptResponse = { sampleType?: string; +}; + +type LanguagePromptResponse = { sampleLanguage?: string; }; +type CreateApiSamplePromptResponse = SampleTypePromptResponse & + LanguagePromptResponse; + function getSampleTypesPrompt( choices: SampleChoice[] -): PromptConfig { +): PromptConfig { return { type: 'rawlist', name: 'sampleType', @@ -32,7 +38,7 @@ function getSampleTypesPrompt( value: choice.id, })), validate: function(input?: string) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { if (input && input.length > 0) { resolve(true); } else { @@ -45,7 +51,7 @@ function getSampleTypesPrompt( function getLanguagesPrompt( choices: string[] -): PromptConfig { +): PromptConfig { return { type: 'rawlist', name: 'sampleLanguage', @@ -55,7 +61,7 @@ function getLanguagesPrompt( value: choice, })), validate: function(input: string | undefined) { - return new Promise(function(resolve, reject) { + return new Promise(function(resolve, reject) { if (input && input.length > 0) { resolve(true); } @@ -70,14 +76,14 @@ export async function createApiSamplePrompt( ): Promise { try { const { samples } = samplesConfig; - const sampleTypeAnswer = await promptUser( + const sampleTypeAnswer = await promptUser( getSampleTypesPrompt(samples) ); const chosenSample = samples.find( sample => sample.id === sampleTypeAnswer.sampleType ); const { languages } = chosenSample!; - const languagesAnswer = await promptUser( + const languagesAnswer = await promptUser( getLanguagesPrompt(languages) ); return { diff --git a/lib/prompts/downloadProjectPrompt.ts b/lib/prompts/downloadProjectPrompt.ts index 4085a1bf6..0ac1e5dd6 100644 --- a/lib/prompts/downloadProjectPrompt.ts +++ b/lib/prompts/downloadProjectPrompt.ts @@ -2,6 +2,7 @@ import { promptUser } from './promptUtils'; import { getAccountId } from '@hubspot/local-dev-lib/config'; import { fetchProjects } from '@hubspot/local-dev-lib/api/projects'; import { logError, ApiErrorContext } from '../errorHandlers/index'; +import { logger } from '@hubspot/local-dev-lib/logger'; import { EXIT_CODES } from '../enums/exitCodes'; import { i18n } from '../lang'; import { Project } from '@hubspot/local-dev-lib/types/Project'; @@ -20,7 +21,8 @@ async function createProjectsList( const { data: projects } = await fetchProjects(accountId); return projects.results; } - return []; + logger.error(i18n(`${i18nKey}.errors.accountIdRequired`)); + process.exit(EXIT_CODES.ERROR); } catch (e) { if (accountId) { logError(e, new ApiErrorContext({ accountId })); diff --git a/types/prompts.ts b/types/prompts.ts index d73da38f1..54860fd67 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -17,7 +17,7 @@ export type PromptChoices = export type PromptWhen = boolean | (() => boolean); -export type PromptOperand = string | number | boolean | string[] | boolean[]; +type PromptOperand = string | number | boolean | string[] | boolean[]; export type PromptConfig = { name: keyof T; From 0253ab1a53904a62b4f6bca4752c1d9ccaede9bc Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 6 Dec 2024 12:46:25 -0500 Subject: [PATCH 12/15] Bump LDL version --- package.json | 2 +- yarn.lock | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3c61e615f..025525207 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "repository": "https://github.com/HubSpot/hubspot-cli", "dependencies": { - "@hubspot/local-dev-lib": "3.0.0", + "@hubspot/local-dev-lib": "3.0.1", "@hubspot/serverless-dev-runtime": "7.0.0", "@hubspot/theme-preview-dev-server": "0.0.9", "@hubspot/ui-extensions-dev-server": "0.8.33", diff --git a/yarn.lock b/yarn.lock index fcbb29434..cff3b90b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1463,10 +1463,10 @@ semver "^6.3.0" unixify "^1.0.0" -"@hubspot/local-dev-lib@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-3.0.0.tgz#2530a9b8567e5078e282caf3f136a6f2cc03c4e1" - integrity sha512-PDyeh2qDZeQFhcIJIVasQKwU0rs7jhRzzMRgk0uQi2SI4HWsSWvDYmTq+Gk16R4K4s9q2Tf/VsN5/Vdo7G1m6Q== +"@hubspot/local-dev-lib@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-3.0.1.tgz#1dd22f439d6e262353f14915a354115bbc1f5f76" + integrity sha512-h1jOmZJNdHZFbrOA5Gn815YCsix8eY81A4dkrUuDZI4MzVaJH3o4RoRalMl+Hr2e35nDrbrbcIR1RMQADPSmwg== dependencies: address "^2.0.1" axios "^1.3.5" @@ -11539,6 +11539,7 @@ wordwrap@^1.0.0: integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 64ec64e3b160db98474cf96fff85513b85b11095 Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 6 Dec 2024 15:07:34 -0500 Subject: [PATCH 13/15] Address feedback p1 --- lang/en.lyaml | 1 + lib/prompts/personalAccessKeyPrompt.ts | 21 +++++++++--------- lib/prompts/projectDevTargetAccountPrompt.ts | 9 +++++--- lib/prompts/sandboxesPrompt.ts | 12 ++++++---- lib/prompts/selectPublicAppPrompt.ts | 20 ++++++++--------- lib/ui/index.ts | 7 ++++-- types/prompts.ts | 23 +++++--------------- 7 files changed, 44 insertions(+), 49 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index 0da24cc7f..bee52c6d2 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1307,6 +1307,7 @@ en: selectAppIdMigrate: "[--appId] Choose an app under {{ accountName }} to migrate:" selectAppIdClone: "[--appId] Choose an app under {{ accountName }} to clone:" errors: + noAccountId: "An account ID is required to select an app." noAppsMigration: "{{#bold}}No apps to migrate{{/bold}}" noAppsClone: "{{#bold}}No apps to clone{{/bold}}" noAppsMigrationMessage: "The selected developer account {{#bold}}{{ accountName }}{{/bold}} doesn't have any apps that can be migrated to the projects framework." diff --git a/lib/prompts/personalAccessKeyPrompt.ts b/lib/prompts/personalAccessKeyPrompt.ts index c1d1df445..7762868ef 100644 --- a/lib/prompts/personalAccessKeyPrompt.ts +++ b/lib/prompts/personalAccessKeyPrompt.ts @@ -37,7 +37,7 @@ type PersonalAccessKeyBrowserOpenPrepResponse = { }; type ScopesPromptResponse = { - scopes: string; + scopes: string[]; }; /** @@ -60,9 +60,9 @@ export async function personalAccessKeyPrompt({ if (account) { url = `${websiteOrigin}/personal-access-key/${account}`; } - const { personalAcessKeyBrowserOpenPrep: shouldOpen } = await promptUser<{ - personalAcessKeyBrowserOpenPrep: boolean; - }>([PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP]); + const { personalAcessKeyBrowserOpenPrep: shouldOpen } = await promptUser< + PersonalAccessKeyBrowserOpenPrepResponse + >([PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP]); if (shouldOpen) { open(url, { url: true }); } else { @@ -82,7 +82,7 @@ export async function personalAccessKeyPrompt({ }; } -export const ACCOUNT_ID: PromptConfig = { +const ACCOUNT_ID: PromptConfig = { name: 'accountId', message: i18n(`${i18nKey}.enterAccountId`), type: 'number', @@ -94,7 +94,7 @@ export const ACCOUNT_ID: PromptConfig = { }, }; -export const CLIENT_ID: PromptConfig = { +const CLIENT_ID: PromptConfig = { name: 'clientId', message: i18n(`${i18nKey}.enterClientId`), validate(val?: string) { @@ -107,7 +107,7 @@ export const CLIENT_ID: PromptConfig = { }, }; -export const CLIENT_SECRET: PromptConfig = { +const CLIENT_SECRET: PromptConfig = { name: 'clientSecret', message: i18n(`${i18nKey}.enterClientSecret`), validate(val?: string) { @@ -122,13 +122,13 @@ export const CLIENT_SECRET: PromptConfig = { }, }; -export const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = { +const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = { name: 'personalAcessKeyBrowserOpenPrep', type: 'confirm', message: i18n(`${i18nKey}.personalAccessKeyBrowserOpenPrompt`), }; -export const PERSONAL_ACCESS_KEY: PromptConfig = { +const PERSONAL_ACCESS_KEY: PromptConfig = { name: 'personalAccessKey', message: i18n(`${i18nKey}.enterPersonalAccessKey`), transformer: (val?: string) => { @@ -149,7 +149,7 @@ export const PERSONAL_ACCESS_KEY: PromptConfig }, }; -export const SCOPES: PromptConfig = { +const SCOPES: PromptConfig = { type: 'checkbox', name: 'scopes', message: i18n(`${i18nKey}.selectScopes`), @@ -157,7 +157,6 @@ export const SCOPES: PromptConfig = { choices: [...OAUTH_SCOPES], }; -// TODO: Type needed? export const OAUTH_FLOW = [ getCliAccountNamePromptConfig(), ACCOUNT_ID, diff --git a/lib/prompts/projectDevTargetAccountPrompt.ts b/lib/prompts/projectDevTargetAccountPrompt.ts index 1fe9a1fbb..8655df1e8 100644 --- a/lib/prompts/projectDevTargetAccountPrompt.ts +++ b/lib/prompts/projectDevTargetAccountPrompt.ts @@ -13,7 +13,10 @@ import { logger } from '@hubspot/local-dev-lib/logger'; import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts'; import { CLIAccount, AccountType } from '@hubspot/local-dev-lib/types/Accounts'; import { Usage } from '@hubspot/local-dev-lib/types/Sandbox'; -import { DeveloperTestAccount } from '@hubspot/local-dev-lib/types/DeveloperTestAccounts'; +import { + DeveloperTestAccount, + FetchDeveloperTestAccountsResponse, +} from '@hubspot/local-dev-lib/types/developerTestAccounts'; import { PromptChoices } from '../../types/prompts'; const i18nKey = 'lib.prompts.projectDevTargetAccountPrompt'; @@ -122,7 +125,7 @@ export async function selectDeveloperTestTargetAccountPrompt( defaultAccountConfig: CLIAccount ): Promise { const defaultAccountId = getAccountId(defaultAccountConfig.name); - let devTestAccountsResponse = undefined; + let devTestAccountsResponse: FetchDeveloperTestAccountsResponse | undefined; try { if (defaultAccountId) { const { data } = await fetchDeveloperTestAccounts(defaultAccountId); @@ -186,7 +189,7 @@ async function selectTargetAccountPrompt( accountType: string, choices: PromptChoices ): Promise { - const accountId = defaultAccountId || undefined; + const accountId = defaultAccountId; const { targetAccountInfo } = await promptUser<{ targetAccountInfo: CLIAccount | DeveloperTestAccount; }>([ diff --git a/lib/prompts/sandboxesPrompt.ts b/lib/prompts/sandboxesPrompt.ts index fbd6c760c..8b2232669 100644 --- a/lib/prompts/sandboxesPrompt.ts +++ b/lib/prompts/sandboxesPrompt.ts @@ -17,6 +17,10 @@ type SandboxTypePromptResponse = { type: string; }; +type DeleteSandboxPromptResponse = { + account: string; +}; + function mapSandboxAccountChoices( portals: CLIAccount[] | null | undefined ): PromptChoices { @@ -66,15 +70,15 @@ export async function sandboxTypePrompt(): Promise { export function deleteSandboxPrompt( promptParentAccount = false -): Promise { +): Promise | void { const accountsList = getConfigAccounts(); const choices = promptParentAccount ? mapNonSandboxAccountChoices(accountsList) : mapSandboxAccountChoices(accountsList); if (!choices.length) { - return Promise.resolve(undefined); + return; } - return promptUser([ + return promptUser([ { name: 'account', message: i18n( @@ -85,7 +89,7 @@ export function deleteSandboxPrompt( type: 'list', pageSize: 20, choices, - default: getConfigDefaultAccount() || undefined, + default: getConfigDefaultAccount(), }, ]); } diff --git a/lib/prompts/selectPublicAppPrompt.ts b/lib/prompts/selectPublicAppPrompt.ts index ecc028900..eb1f62bba 100644 --- a/lib/prompts/selectPublicAppPrompt.ts +++ b/lib/prompts/selectPublicAppPrompt.ts @@ -19,13 +19,15 @@ async function fetchPublicAppOptions( isMigratingApp = false ): Promise { try { - let publicApps: PublicApp[] = []; - if (accountId) { - const { - data: { results: apps }, - } = await fetchPublicAppsForPortal(accountId); - publicApps = apps; + if (!accountId) { + logger.error(i18n(`${i18nKey}.errors.noAccountId`)); + process.exit(EXIT_CODES.ERROR); } + + const { + data: { results: publicApps }, + } = await fetchPublicAppsForPortal(accountId); + const filteredPublicApps = publicApps.filter( app => !app.projectId && !app.sourceId ); @@ -53,11 +55,7 @@ async function fetchPublicAppOptions( } return filteredPublicApps; } catch (error) { - if (accountId) { - logError(error, { accountId }); - } else { - logError(error); - } + logError(error, accountId ? { accountId } : undefined); logger.error(i18n(`${i18nKey}.errors.errorFetchingApps`)); process.exit(EXIT_CODES.ERROR); } diff --git a/lib/ui/index.ts b/lib/ui/index.ts index edbeac7e7..465df3ff8 100644 --- a/lib/ui/index.ts +++ b/lib/ui/index.ts @@ -54,8 +54,11 @@ export function uiLink(linkText: string, url: string): string { : `${linkText}: ${encodedUrl}`; } -export function uiAccountDescription(accountId?: number, bold = true): string { - const account = getAccountConfig(accountId); +export function uiAccountDescription( + accountId?: number | null, + bold = true +): string { + const account = getAccountConfig(accountId || undefined); let message; if (account && account.accountType) { message = `${account.name} [${ diff --git a/types/prompts.ts b/types/prompts.ts index abd3fabb2..2a516a8e7 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -1,5 +1,3 @@ -import { DeveloperTestAccount } from '@hubspot/local-dev-lib/types/developerTestAccounts'; - export type GenericPromptResponse = { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; @@ -18,24 +16,15 @@ export type PromptChoices = Array< | string | { name: string; - value?: - | string - | number - | { - [key: string]: - | string - | number - | boolean - | DeveloperTestAccount - | null; - }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value?: any; disabled?: string | boolean; } >; export type PromptWhen = boolean | (() => boolean); -type PromptOperand = string | number | boolean | string[] | boolean[]; +type PromptOperand = string | number | boolean | string[] | boolean[] | null; export type PromptConfig = { name: keyof T; @@ -46,10 +35,8 @@ export type PromptConfig = { pageSize?: number; default?: PromptOperand | ((answers: T) => PromptOperand); transformer?: (input: string) => string | undefined; - validate?: - | ((answer?: string) => PromptOperand | Promise) - | ((answer?: number) => PromptOperand | Promise) - | ((answer: string[]) => PromptOperand | Promise); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + validate?: (answer?: unknown | any) => PromptOperand | Promise; mask?: string; filter?: (input: string) => string; }; From 1d3fbba59163fc76535c881196f18e41623b8c4b Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Fri, 6 Dec 2024 15:31:16 -0500 Subject: [PATCH 14/15] Fix return type --- lib/prompts/sandboxesPrompt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/prompts/sandboxesPrompt.ts b/lib/prompts/sandboxesPrompt.ts index 8b2232669..1046875a8 100644 --- a/lib/prompts/sandboxesPrompt.ts +++ b/lib/prompts/sandboxesPrompt.ts @@ -9,7 +9,7 @@ import { getConfigAccounts, } from '@hubspot/local-dev-lib/config'; import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; -import { PromptChoices, GenericPromptResponse } from '../../types/prompts'; +import { PromptChoices } from '../../types/prompts'; const i18nKey = 'lib.prompts.sandboxesPrompt'; @@ -70,7 +70,7 @@ export async function sandboxTypePrompt(): Promise { export function deleteSandboxPrompt( promptParentAccount = false -): Promise | void { +): Promise | void { const accountsList = getConfigAccounts(); const choices = promptParentAccount ? mapNonSandboxAccountChoices(accountsList) From ad7654ea3612b8f2294af6d5063d4e2360a2c5ec Mon Sep 17 00:00:00 2001 From: Allison Kemmerle Date: Wed, 11 Dec 2024 12:04:05 -0500 Subject: [PATCH 15/15] Address feedback 12-11 --- lang/en.lyaml | 1 + lib/prompts/createTemplatePrompt.ts | 6 +++--- lib/prompts/projectDevTargetAccountPrompt.ts | 7 +++++++ types/prompts.ts | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lang/en.lyaml b/lang/en.lyaml index d186b316f..217d09d75 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -1172,6 +1172,7 @@ en: developerTestAccountLimit: "Your account reached the limit of {{ limit }} developer test accounts." confirmDefaultAccount: "Continue testing on {{#bold}}{{ accountName }} ({{ accountType }}){{/bold}}? (Y/n)" confirmUseExistingDeveloperTestAccount: "Continue with {{ accountName }}? This account isn't currently connected to the HubSpot CLI. By continuing, you'll be prompted to generate a personal access key and connect it." + noAccountId: "No account ID found for the selected account. Please try again." projectLogsPrompt: functionName: "[--function] Select function in {{#bold}}{{projectName}}{{/bold}} project" setAsDefaultAccountPrompt: diff --git a/lib/prompts/createTemplatePrompt.ts b/lib/prompts/createTemplatePrompt.ts index 9a8b0e524..ecfae6da1 100644 --- a/lib/prompts/createTemplatePrompt.ts +++ b/lib/prompts/createTemplatePrompt.ts @@ -4,7 +4,7 @@ import { PromptChoices, PromptConfig } from '../../types/prompts'; const i18nKey = 'lib.prompts.createTemplatePrompt'; -const templateTypeChoices: PromptChoices = [ +const templateTypeChoices = [ { name: 'page', value: 'page-template' }, { name: 'email', value: 'email-template' }, { name: 'partial', value: 'partial' }, @@ -12,10 +12,10 @@ const templateTypeChoices: PromptChoices = [ { name: 'blog listing', value: 'blog-listing-template' }, { name: 'blog post', value: 'blog-post-template' }, { name: 'search results', value: 'search-template' }, -] as const; +] satisfies PromptChoices; interface CreateTemplatePromptResponse { - templateType: typeof templateTypeChoices[number]; + templateType: typeof templateTypeChoices[number]['value']; } const TEMPLATE_TYPE_PROMPT: PromptConfig = { diff --git a/lib/prompts/projectDevTargetAccountPrompt.ts b/lib/prompts/projectDevTargetAccountPrompt.ts index 8655df1e8..ad56fdf58 100644 --- a/lib/prompts/projectDevTargetAccountPrompt.ts +++ b/lib/prompts/projectDevTargetAccountPrompt.ts @@ -18,6 +18,7 @@ import { FetchDeveloperTestAccountsResponse, } from '@hubspot/local-dev-lib/types/developerTestAccounts'; import { PromptChoices } from '../../types/prompts'; +import { EXIT_CODES } from '../enums/exitCodes'; const i18nKey = 'lib.prompts.projectDevTargetAccountPrompt'; @@ -64,6 +65,9 @@ export async function selectSandboxTargetAccountPrompt( if (defaultAccountId) { const { data } = await getSandboxUsageLimits(defaultAccountId); sandboxUsage = data.usage; + } else { + logger.error(`${i18nKey}.noAccountId`); + process.exit(EXIT_CODES.ERROR); } } catch (err) { logger.debug('Unable to fetch sandbox usage limits: ', err); @@ -130,6 +134,9 @@ export async function selectDeveloperTestTargetAccountPrompt( if (defaultAccountId) { const { data } = await fetchDeveloperTestAccounts(defaultAccountId); devTestAccountsResponse = data; + } else { + logger.error(`${i18nKey}.noAccountId`); + process.exit(EXIT_CODES.ERROR); } } catch (err) { logger.debug('Unable to fetch developer test account usage limits: ', err); diff --git a/types/prompts.ts b/types/prompts.ts index 2a516a8e7..db703d253 100644 --- a/types/prompts.ts +++ b/types/prompts.ts @@ -36,7 +36,7 @@ export type PromptConfig = { default?: PromptOperand | ((answers: T) => PromptOperand); transformer?: (input: string) => string | undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any - validate?: (answer?: unknown | any) => PromptOperand | Promise; + validate?: (answer?: any) => PromptOperand | Promise; mask?: string; filter?: (input: string) => string; };