diff --git a/commands/customObject.ts b/commands/customObject.ts index fd2f67566..5cf53913b 100644 --- a/commands/customObject.ts +++ b/commands/customObject.ts @@ -1,7 +1,5 @@ // @ts-nocheck const { - addConfigOptions, - addAccountOptions, addGlobalOptions, } = require('../lib/commonOpts'); const schemaCommand = require('./customObject/schema'); @@ -12,7 +10,7 @@ const { uiBetaTag, uiLink } = require('../lib/ui'); const i18nKey = 'commands.customObject'; -exports.command = ['custom-object', 'custom', 'co']; +exports.command = ['custom-object', 'custom-objects', 'co']; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); const logBetaMessage = () => { @@ -27,8 +25,6 @@ const logBetaMessage = () => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); addGlobalOptions(yargs); yargs diff --git a/commands/customObject/create.ts b/commands/customObject/create.ts index a63de888a..ec32235e7 100644 --- a/commands/customObject/create.ts +++ b/commands/customObject/create.ts @@ -1,4 +1,6 @@ // @ts-nocheck +import { inputPrompt } from '../../lib/prompts/promptUtils'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../lib/errorHandlers/index'); const { getAbsoluteFilePath } = require('@hubspot/local-dev-lib/path'); @@ -15,23 +17,31 @@ const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.customObject.subcommands.create'; const { EXIT_CODES } = require('../../lib/enums/exitCodes'); -exports.command = 'create '; +exports.command = 'create [name]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { definition, name, derivedAccountId } = options; + const { path, name: providedName, derivedAccountId } = options; + let definitionPath = path; await loadAndValidateOptions(options); trackCommandUsage('custom-object-batch-create', null, derivedAccountId); - const filePath = getAbsoluteFilePath(definition); + if (!definitionPath) { + definitionPath = await inputPrompt(i18n(`${i18nKey}.inputPath`)); + } + + const filePath = getAbsoluteFilePath(definitionPath); const objectJson = checkAndConvertToJson(filePath); if (!objectJson) { process.exit(EXIT_CODES.ERROR); } + const name = + providedName || (await inputPrompt(i18n(`${i18nKey}.inputSchema`))); + try { await batchCreateObjects(derivedAccountId, name, objectJson); logger.success(i18n(`${i18nKey}.success.objectsCreated`)); @@ -39,20 +49,20 @@ exports.handler = async options => { logError(e, { accountId: derivedAccountId }); logger.error( i18n(`${i18nKey}.errors.creationFailed`, { - definition, + definition: definitionPath, }) ); } }; exports.builder = yargs => { - yargs.positional('name', { - describe: i18n(`${i18nKey}.positionals.name.describe`), - type: 'string', - }); - - yargs.positional('definition', { - describe: i18n(`${i18nKey}.positionals.definition.describe`), - type: 'string', - }); + yargs + .positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }) + .option('path', { + describe: i18n(`${i18nKey}.options.path.describe`), + type: 'string', + }); }; diff --git a/commands/customObject/schema.ts b/commands/customObject/schema.ts index 1a7cf6910..596598750 100644 --- a/commands/customObject/schema.ts +++ b/commands/customObject/schema.ts @@ -9,7 +9,7 @@ const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.customObject.subcommands.schema'; -exports.command = 'schema'; +exports.command = ['schema', 'schemas']; exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { yargs diff --git a/commands/customObject/schema/create.ts b/commands/customObject/schema/create.ts index 074dd5cbb..248bd2631 100644 --- a/commands/customObject/schema/create.ts +++ b/commands/customObject/schema/create.ts @@ -28,17 +28,17 @@ const { i18n } = require('../../../lib/lang'); const i18nKey = 'commands.customObject.subcommands.schema.subcommands.create'; const { EXIT_CODES } = require('../../../lib/enums/exitCodes'); -exports.command = 'create '; +exports.command = 'create'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { definition, derivedAccountId } = options; + const { path, derivedAccountId } = options; await loadAndValidateOptions(options); trackCommandUsage('custom-object-schema-create', null, derivedAccountId); - const filePath = getAbsoluteFilePath(definition); + const filePath = getAbsoluteFilePath(path); const schemaJson = checkAndConvertToJson(filePath); if (!schemaJson) { process.exit(EXIT_CODES.ERROR); @@ -66,7 +66,7 @@ exports.handler = async options => { logError(e, { accountId: derivedAccountId }); logger.error( i18n(`${i18nKey}.errors.creationFailed`, { - definition, + definition: path, }) ); } @@ -75,8 +75,9 @@ exports.handler = async options => { exports.builder = yargs => { addTestingOptions(yargs); - yargs.positional('definition', { - describe: i18n(`${i18nKey}.positionals.definition.describe`), + yargs.option('path', { + describe: i18n(`${i18nKey}.options.definition.describe`), type: 'string', + required: true, }); }; diff --git a/commands/customObject/schema/delete.ts b/commands/customObject/schema/delete.ts index 1c66590d6..427eb0a79 100644 --- a/commands/customObject/schema/delete.ts +++ b/commands/customObject/schema/delete.ts @@ -1,4 +1,8 @@ // @ts-nocheck +import { EXIT_CODES } from '../../../lib/enums/exitCodes'; +import { confirmPrompt, listPrompt } from '../../../lib/prompts/promptUtils'; +import { fetchObjectSchemas } from '@hubspot/local-dev-lib/api/customObjects'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { loadAndValidateOptions } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); @@ -10,17 +14,37 @@ const { logError } = require('../../../lib/errorHandlers'); const i18nKey = 'commands.customObject.subcommands.schema.subcommands.delete'; -exports.command = 'delete '; +exports.command = 'delete [name]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { name, derivedAccountId } = options; + const { name: providedName, force, derivedAccountId } = options; await loadAndValidateOptions(options); trackCommandUsage('custom-object-schema-delete', null, derivedAccountId); + let name; try { + const { + data: { results }, + } = await fetchObjectSchemas(derivedAccountId); + const schemaNames = results?.map(({ name: schemaName }) => schemaName); + name = + providedName || + (await listPrompt(i18n(`${i18nKey}.selectSchema`), { + choices: schemaNames, + })); + + const shouldDelete = + force || + (await confirmPrompt(i18n(`${i18nKey}.confirmDelete`, { name }))); + + if (!shouldDelete) { + logger.info(i18n(`${i18nKey}.deleteCancelled`, { name })); + return process.exit(EXIT_CODES.SUCCESS); + } + await deleteObjectSchema(derivedAccountId, name); logger.success( i18n(`${i18nKey}.success.delete`, { @@ -38,12 +62,16 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.example([ - ['$0 schema delete schemaName', i18n(`${i18nKey}.examples.default`)], - ]); - - yargs.positional('name', { - describe: i18n(`${i18nKey}.positionals.name.describe`), - type: 'string', - }); + yargs + .example([ + ['$0 schema delete schemaName', i18n(`${i18nKey}.examples.default`)], + ]) + .positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }) + .option('force', { + describe: i18n(`${i18nKey}.options.force.describe`), + type: 'boolean', + }); }; diff --git a/commands/customObject/schema/fetch-all.ts b/commands/customObject/schema/fetch-all.ts index 63fb5a5c5..e3e14d527 100644 --- a/commands/customObject/schema/fetch-all.ts +++ b/commands/customObject/schema/fetch-all.ts @@ -1,4 +1,6 @@ // @ts-nocheck +import { inputPrompt } from '../../../lib/prompts/promptUtils'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { loadAndValidateOptions } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); @@ -18,16 +20,18 @@ exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { await loadAndValidateOptions(options); - const { derivedAccountId } = options; + const { derivedAccountId, dest: providedDest } = options; trackCommandUsage('custom-object-schema-fetch-all', null, derivedAccountId); try { - const schemas = await downloadSchemas(derivedAccountId, options.dest); + const dest = + providedDest || (await inputPrompt(i18n(`${i18nKey}.inputDest`))); + const schemas = await downloadSchemas(derivedAccountId, dest); logSchemas(schemas); logger.success( i18n(`${i18nKey}.success.fetch`, { - path: getResolvedPath(options.dest), + path: getResolvedPath(dest), }) ); } catch (e) { @@ -37,16 +41,19 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.example([ - ['$0 custom-object schema fetch-all', i18n(`${i18nKey}.examples.default`)], - [ - '$0 custom-object schema fetch-all my/folder', - i18n(`${i18nKey}.examples.specifyPath`), - ], - ]); - - yargs.positional('dest', { - describe: i18n(`${i18nKey}.positionals.dest.describe`), - type: 'string', - }); + yargs + .example([ + [ + '$0 custom-object schema fetch-all', + i18n(`${i18nKey}.examples.default`), + ], + [ + '$0 custom-object schema fetch-all my/folder', + i18n(`${i18nKey}.examples.specifyPath`), + ], + ]) + .positional('dest', { + describe: i18n(`${i18nKey}.positionals.dest.describe`), + type: 'string', + }); }; diff --git a/commands/customObject/schema/fetch.ts b/commands/customObject/schema/fetch.ts index 60f9b2ea9..73aba85e6 100644 --- a/commands/customObject/schema/fetch.ts +++ b/commands/customObject/schema/fetch.ts @@ -1,4 +1,7 @@ // @ts-nocheck +import { inputPrompt, listPrompt } from '../../../lib/prompts/promptUtils'; +import { fetchObjectSchemas } from '@hubspot/local-dev-lib/api/customObjects'; + const path = require('path'); const { isConfigFlagEnabled } = require('@hubspot/local-dev-lib/config'); const { logger } = require('@hubspot/local-dev-lib/logger'); @@ -17,17 +20,32 @@ const { logError } = require('../../../lib/errorHandlers'); const i18nKey = 'commands.customObject.subcommands.schema.subcommands.fetch'; -exports.command = 'fetch [dest]'; +exports.command = 'fetch [name] [dest]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { name, dest, derivedAccountId } = options; + const { name: providedName, dest: providedDest, derivedAccountId } = options; await loadAndValidateOptions(options); trackCommandUsage('custom-object-schema-fetch', null, derivedAccountId); + let name; try { + const { + data: { results }, + } = await fetchObjectSchemas(derivedAccountId); + const schemaNames = results?.map(({ name: schemaName }) => schemaName); + + name = + providedName || + (await listPrompt(i18n(`${i18nKey}.selectSchema`), { + choices: schemaNames, + })); + + const dest = + providedDest || (await inputPrompt(i18n(`${i18nKey}.inputDest`))); + if (isConfigFlagEnabled(CONFIG_FLAGS.USE_CUSTOM_OBJECT_HUBFILE)) { const fullpath = path.resolve(getCwd(), dest); await fetchSchema(derivedAccountId, name, fullpath); @@ -56,24 +74,23 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.example([ - [ - '$0 custom-object schema fetch schemaName', - i18n(`${i18nKey}.examples.default`), - ], - [ - '$0 custom-object schema fetch schemaName my/folder', - i18n(`${i18nKey}.examples.specifyPath`), - ], - ]); - - yargs.positional('name', { - describe: i18n(`${i18nKey}.positionals.name.describe`), - type: 'string', - }); - - yargs.positional('dest', { - describe: i18n(`${i18nKey}.positionals.dest.describe`), - type: 'string', - }); + yargs + .example([ + [ + '$0 custom-object schema fetch schemaName', + i18n(`${i18nKey}.examples.default`), + ], + [ + '$0 custom-object schema fetch schemaName my/folder', + i18n(`${i18nKey}.examples.specifyPath`), + ], + ]) + .positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }) + .positional('dest', { + describe: i18n(`${i18nKey}.positionals.dest.describe`), + type: 'string', + }); }; diff --git a/commands/customObject/schema/update.ts b/commands/customObject/schema/update.ts index e4cbc90c7..577d58902 100644 --- a/commands/customObject/schema/update.ts +++ b/commands/customObject/schema/update.ts @@ -1,4 +1,7 @@ // @ts-nocheck +import { fetchObjectSchemas } from '@hubspot/local-dev-lib/api/customObjects'; +import { listPrompt } from '../../../lib/prompts/promptUtils'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../../lib/errorHandlers/index'); const { getAbsoluteFilePath } = require('@hubspot/local-dev-lib/path'); @@ -28,23 +31,34 @@ const { i18n } = require('../../../lib/lang'); const i18nKey = 'commands.customObject.subcommands.schema.subcommands.update'; const { EXIT_CODES } = require('../../../lib/enums/exitCodes'); -exports.command = 'update '; +exports.command = 'update [name]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { definition, name, derivedAccountId } = options; + const { path, name: providedName, derivedAccountId } = options; await loadAndValidateOptions(options); trackCommandUsage('custom-object-schema-update', null, derivedAccountId); - const filePath = getAbsoluteFilePath(definition); + const filePath = getAbsoluteFilePath(path); const schemaJson = checkAndConvertToJson(filePath); if (!schemaJson) { process.exit(EXIT_CODES.ERROR); } + let name = providedName; try { + const { + data: { results }, + } = await fetchObjectSchemas(derivedAccountId); + const schemaNames = results?.map(({ name: schemaName }) => schemaName); + + name = + providedName || + (await listPrompt(i18n(`${i18nKey}.selectSchema`), { + choices: schemaNames, + })); if (isConfigFlagEnabled(CONFIG_FLAGS.USE_CUSTOM_OBJECT_HUBFILE)) { await updateSchemaFromHubFile(derivedAccountId, filePath); logger.success( @@ -70,7 +84,7 @@ exports.handler = async options => { logError(e, { accountId: derivedAccountId }); logger.error( i18n(`${i18nKey}.errors.update`, { - definition, + definition: path, }) ); } @@ -79,13 +93,14 @@ exports.handler = async options => { exports.builder = yargs => { addTestingOptions(yargs); - yargs.positional('name', { - describe: i18n(`${i18nKey}.positionals.name.describe`), - type: 'string', - }); - - yargs.positional('definition', { - describe: i18n(`${i18nKey}.positionals.definition.describe`), - type: 'string', - }); + yargs + .positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }) + .option('path', { + describe: i18n(`${i18nKey}.options.path.describe`), + type: 'string', + required: true, + }); }; diff --git a/lang/en.lyaml b/lang/en.lyaml index 88e6e4bb5..3f342f83a 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -199,35 +199,38 @@ en: nameRequired: "The \"name\" argument is required when creating a Template." customObject: betaMessage: "The Custom Object CLI is currently in beta and is subject to change." - describe: "Manage Custom Objects. This feature is currently in beta and the CLI contract is subject to change." + describe: "Commands for managing custom objects." seeMoreLink: "View our docs to find out more." subcommands: create: - describe: "Create custom object instances" + describe: "Create custom object instances." errors: creationFailed: "Object creation from {{ definition }} failed" - positionals: - definition: + options: + path: describe: "Local path to the JSON file containing an array of object definitions" + positionals: name: describe: "Schema name to add the object instance to" success: objectsCreated: "Objects created" + inputSchema: "What would you like to name the schema?" + inputPath: "[--path] Where is the JSON file containing the object definitions?" schema: - describe: "Manage custom object schemas" + describe: "Commands for managing custom object schemas." subcommands: create: - describe: "Create a custom object schema" + describe: "Create a custom object schema." errors: creationFailed: "Schema creation from {{ definition }} failed" - positionals: + options: definition: describe: "Local path to the JSON file containing the schema definition" success: schemaCreated: "Your schema has been created in account \"{{ accountId }}\"" schemaViewable: "Schema can be viewed at {{ url }}" delete: - describe: "Delete a custom object schema" + describe: "Delete a custom object schema." errors: delete: "Unable to delete {{ name }}" examples: @@ -235,10 +238,16 @@ en: positionals: name: describe: "Name of the target schema" + options: + force: + describe: "Force the deletion of the schema." success: delete: "Successfully initiated deletion of {{ name }}" + confirmDelete: "Are you sure you want to delete the schema \"{{ name }}\"?" + deleteCancelled: "Deletion of schema \"{{ name }}\" cancelled." + selectSchema: "Which schema would you like to delete?" fetchAll: - describe: "Fetch all custom object schemas for an account" + describe: "Fetch all custom object schemas for an account." errors: fetch: "Unable to fetch schemas" examples: @@ -249,8 +258,9 @@ en: describe: "Local folder where schemas will be written" success: fetch: "Saved schemas to {{ path }}" + inputDest: "Where would you like to save the schemas?" fetch: - describe: "Fetch a custom object schema" + describe: "Fetch a custom object schema." errors: fetch: "Unable to fetch {{ name }}" examples: @@ -261,25 +271,29 @@ en: describe: "Local folder where schema will be written" name: describe: "Name of the target schema" + selectSchema: "Which schema would you like to fetch?" + inputDest: "What would you like to name the destination file?" success: save: "The schema \"{{ name }}\" has been saved to \"{{ path }}\"" savedToPath: "Saved schema to {{ path }}" list: - describe: "List schemas available on your account" + describe: "List custom object schemas." errors: list: "Unable to list schemas" update: - describe: "Update an existing custom object schema" + describe: "Update an existing custom object schema." errors: update: "Schema update from {{ definition }} failed" - positionals: - definition: + options: + path: describe: "Local path to the JSON file containing the schema definition" + positionals: name: describe: "Name of the target schema" success: update: "Your schema has been updated in account \"{{ accountId }}\"" viewAtUrl: "Schema can be viewed at {{ url }}" + selectSchema: "Which schema would you like to update?" doctor: describe: "Retrieve diagnostic information about your local HubSpot configurations." options: diff --git a/lib/prompts/promptUtils.ts b/lib/prompts/promptUtils.ts index e2eb9fd60..43220fb9d 100644 --- a/lib/prompts/promptUtils.ts +++ b/lib/prompts/promptUtils.ts @@ -42,3 +42,22 @@ export async function listPrompt( ]); return choice; } + +export async function inputPrompt( + message: string, + { + when, + }: { + when?: boolean | (() => boolean); + } = {} +): Promise { + const { input } = await promptUser([ + { + name: 'input', + type: 'input', + message, + when, + }, + ]); + return input; +}