diff --git a/acceptance-tests/README.md b/acceptance-tests/README.md index a1221d348..6e5fa66fd 100644 --- a/acceptance-tests/README.md +++ b/acceptance-tests/README.md @@ -6,7 +6,7 @@ This project tests our CLI's `hs <*>` commands as if they were being used by an The main test execution is kicked off by running `yarn test-cli` from the root of `hubspot-cli`. This will run the test suites defined in the `tests` directory. -Note that if you are testing against a QA portal, not a PROD one, you'll need to run `yarn test-qa`. There is still an outstanding issue with this because we attempt to add the `--qa` flag to all `hs` commands, however it is not available for all commands. +Note that if you are testing against a QA account, not a PROD one, you'll need to run `yarn test-qa`. There is still an outstanding issue with this because we attempt to add the `--qa` flag to all `hs` commands, however it is not available for all commands. ### Setup diff --git a/acceptance-tests/lib/TestState.ts b/acceptance-tests/lib/TestState.ts index 87dd7190a..73aa6346e 100644 --- a/acceptance-tests/lib/TestState.ts +++ b/acceptance-tests/lib/TestState.ts @@ -45,7 +45,7 @@ export class TestState { async initializeAuth() { try { await this.cli.executeWithTestConfig( - ['init'], + ['init', '--disable-tracking'], getInitPromptSequence(this.getPAK()) ); diff --git a/acceptance-tests/lib/cmd.ts b/acceptance-tests/lib/cmd.ts index f4056f22d..c6424fbdf 100644 --- a/acceptance-tests/lib/cmd.ts +++ b/acceptance-tests/lib/cmd.ts @@ -64,7 +64,7 @@ function executeWithInput( const { env, timeout = 1000, maxTimeout = 30000 } = opts; const childProcess = createProcess(config, args, env); - childProcess.stdin.setEncoding('utf-8'); + childProcess.stdin!.setEncoding('utf-8'); let currentInputTimeout: NodeJS.Timeout; let killIOTimeout: NodeJS.Timeout; @@ -78,7 +78,7 @@ function executeWithInput( } if (!inputs.length) { - childProcess.stdin.end(); + childProcess.stdin!.end(); // Set a timeout to wait for CLI response. If CLI takes longer than // maxTimeout to respond, kill the childProcess and notify user @@ -94,7 +94,7 @@ function executeWithInput( if (typeof inputs[0] === 'function') { await inputs[0](); } else { - childProcess.stdin.write(inputs[0]); + childProcess.stdin!.write(inputs[0]); } if (config.debug) { @@ -106,14 +106,14 @@ function executeWithInput( }; // Get errors from CLI for debugging - childProcess.stderr.on('data', (err: unknown) => { + childProcess.stderr!.on('data', (err: unknown) => { if (config.debug) { console.log('error:', String(err)); } }); // Get output from CLI for debugging - childProcess.stdout.on('data', (data: unknown) => { + childProcess.stdout!.on('data', (data: unknown) => { if (config.debug) { console.log('output:', String(data)); } @@ -123,7 +123,6 @@ function executeWithInput( const handleStderr = (err: unknown) => { // Ignore any allowed errors so tests can continue const allowedErrors = [ - 'DeprecationWarning', // Ignore package deprecation warnings. '[WARNING]', // Ignore our own CLI warning messages ]; @@ -134,12 +133,12 @@ function executeWithInput( } // Resubscribe if we ignored this error - childProcess.stderr.once('data', handleStderr); + childProcess.stderr!.once('data', handleStderr); return; } // If the childProcess errors out, stop all the pending inputs - childProcess.stdin.end(); + childProcess.stdin!.end(); if (currentInputTimeout) { clearTimeout(currentInputTimeout); @@ -149,10 +148,10 @@ function executeWithInput( reject(error); }; - childProcess.stderr.once('data', handleStderr); + childProcess.stderr!.once('data', handleStderr); childProcess.on('error', reject); - childProcess.stdout.pipe( + childProcess.stdout!.pipe( concat((result: unknown) => { if (killIOTimeout) { clearTimeout(killIOTimeout); diff --git a/acceptance-tests/tests/commands/config.spec.ts b/acceptance-tests/tests/commands/config.spec.ts index c8fd313d2..52032f7e4 100644 --- a/acceptance-tests/tests/commands/config.spec.ts +++ b/acceptance-tests/tests/commands/config.spec.ts @@ -15,7 +15,7 @@ describe('hs config', () => { }); describe('hs config set', () => { - it('should set the default mode to draft', async () => { + it('should set the default CMS publish mode to draft', async () => { await testState.cli.executeWithTestConfig( ['config', 'set'], [ENTER, DOWN, ENTER] @@ -23,7 +23,7 @@ describe('hs config', () => { const parsedConfig = testState.getParsedConfig(); - expect(parsedConfig.defaultMode).toEqual('draft'); + expect(parsedConfig.defaultCmsPublishMode).toEqual('draft'); }); }); }); diff --git a/acceptance-tests/tests/commands/create.spec.ts b/acceptance-tests/tests/commands/create.spec.ts index 0276d29c4..97717f628 100644 --- a/acceptance-tests/tests/commands/create.spec.ts +++ b/acceptance-tests/tests/commands/create.spec.ts @@ -69,9 +69,9 @@ describe('hs create', () => { }); it('creates a module', async () => { - await testState.cli.execute( + await testState.cli.executeWithTestConfig( ['create', 'module', FOLDERS.module.name], - ['label', ENTER, ENTER, ENTER, 'y', ENTER] + ['label', ENTER, ENTER, ENTER, 'y', ENTER, ENTER] ); expect(testState.existsInTestOutputDirectory(FOLDERS.module.folder)).toBe( @@ -80,7 +80,7 @@ describe('hs create', () => { }); it('creates a template', async () => { - await testState.cli.execute( + await testState.cli.executeWithTestConfig( ['create', 'template', FOLDERS.template.name], [ENTER] ); @@ -90,35 +90,44 @@ describe('hs create', () => { }); it('website-theme', async () => { - await testState.cli.execute(['create', FOLDERS.websiteTheme.name]); + await testState.cli.executeWithTestConfig([ + 'create', + FOLDERS.websiteTheme.name, + ]); expect( testState.existsInTestOutputDirectory(FOLDERS.websiteTheme.folder) ).toBe(true); }); it('react-app', async () => { - await testState.cli.execute(['create', FOLDERS.reactApp.name]); + await testState.cli.executeWithTestConfig([ + 'create', + FOLDERS.reactApp.name, + ]); expect(testState.existsInTestOutputDirectory(FOLDERS.reactApp.folder)).toBe( true ); }); it('vue-app', async () => { - await testState.cli.execute(['create', FOLDERS.vueApp.name]); + await testState.cli.executeWithTestConfig(['create', FOLDERS.vueApp.name]); expect(testState.existsInTestOutputDirectory(FOLDERS.vueApp.folder)).toBe( true ); }); it('webpack-serverless', async () => { - await testState.cli.execute(['create', FOLDERS.webpackServerless.name]); + await testState.cli.executeWithTestConfig([ + 'create', + FOLDERS.webpackServerless.name, + ]); expect( testState.existsInTestOutputDirectory(FOLDERS.webpackServerless.folder) ).toBe(true); }); it('api-sample', async () => { - await testState.cli.execute( + await testState.cli.executeWithTestConfig( ['create', FOLDERS.apiSample.name, FOLDERS.apiSample.name], [ENTER, ENTER] ); @@ -129,14 +138,14 @@ describe('hs create', () => { }); it('app', async () => { - await testState.cli.execute(['create', FOLDERS.app.name]); + await testState.cli.executeWithTestConfig(['create', FOLDERS.app.name]); expect(testState.existsInTestOutputDirectory(FOLDERS.app.folder)).toBe( true ); }); it('function', async () => { - await testState.cli.execute( + await testState.cli.executeWithTestConfig( ['create', 'function'], [ FOLDERS.function.name, diff --git a/acceptance-tests/tests/commands/hs.spec.ts b/acceptance-tests/tests/commands/hs.spec.ts new file mode 100644 index 000000000..73494eb38 --- /dev/null +++ b/acceptance-tests/tests/commands/hs.spec.ts @@ -0,0 +1,36 @@ +import { describe, beforeAll, it, expect, afterAll } from 'vitest'; +import { TestState } from '../../lib/TestState'; + +describe('hs', () => { + let testState: TestState; + + beforeAll(async () => { + testState = new TestState(); + }); + + afterAll(() => { + testState.cleanup(); + }); + + describe('hs', () => { + it('should log out the help message', async () => { + const output = await testState.cli.executeWithTestConfig([]); + + expect(output).toContain( + 'The command line interface to interact with HubSpot' + ); + }); + + it('should log out the help message when --help is passed', async () => { + const output = await testState.cli.executeWithTestConfig(['--help']); + + expect(output).toContain( + 'The command line interface to interact with HubSpot' + ); + }); + + it('should not throw when --version is passed', async () => { + await testState.cli.executeWithTestConfig(['--version']); + }); + }); +}); diff --git a/acceptance-tests/tests/commands/projectCreate.spec.ts b/acceptance-tests/tests/commands/projectCreate.spec.ts index bb47f320b..89a689fdc 100644 --- a/acceptance-tests/tests/commands/projectCreate.spec.ts +++ b/acceptance-tests/tests/commands/projectCreate.spec.ts @@ -28,7 +28,7 @@ describe('hs project create', () => { 'project', 'create', `--name="${PROJECT_FOLDER}"`, - `--location="${PROJECT_FOLDER}"`, + `--dest="${PROJECT_FOLDER}"`, '--template="getting-started-private-app"', ]); expect(testState.existsInTestOutputDirectory(PROJECT_FOLDER)).toBe(true); diff --git a/acceptance-tests/tests/workflows/accountManagementFlow.spec.ts b/acceptance-tests/tests/workflows/accountManagementFlow.spec.ts index 8538dcdc5..523d1fdd6 100644 --- a/acceptance-tests/tests/workflows/accountManagementFlow.spec.ts +++ b/acceptance-tests/tests/workflows/accountManagementFlow.spec.ts @@ -27,7 +27,7 @@ describe('Account Management Flow', () => { describe('hs init', () => { it('should generate a config file', async () => { await testState.cli.executeWithTestConfig( - ['init'], + ['init', '--disable-tracking'], getInitPromptSequence(testState.getPAK(), accountName) ); @@ -73,12 +73,11 @@ describe('Account Management Flow', () => { describe('hs accounts list', () => { it('should not list the removed authenticated account', async () => { - const output = await testState.cli.executeWithTestConfig([ - 'accounts', - 'list', - ]); - - expect(output).not.toContain(accountName); + await expect(() => + testState.cli.executeWithTestConfig(['accounts', 'list']) + ).rejects.toThrow( + /There are no accounts defined in the configuration file/ + ); }); }); diff --git a/acceptance-tests/tests/workflows/cmsTemplateFlow.spec.ts b/acceptance-tests/tests/workflows/cmsTemplateFlow.spec.ts index 2030e081f..680b11234 100644 --- a/acceptance-tests/tests/workflows/cmsTemplateFlow.spec.ts +++ b/acceptance-tests/tests/workflows/cmsTemplateFlow.spec.ts @@ -21,7 +21,7 @@ describe('CMS Template Flow', () => { describe('hs create', () => { it('should create a CMS template', async () => { - await testState.cli.execute( + await testState.cli.executeWithTestConfig( ['create', 'template', TEMPLATE_NAME], [ENTER] ); diff --git a/acceptance-tests/tests/workflows/secretsFlow.spec.ts b/acceptance-tests/tests/workflows/secretsFlow.spec.ts index f6d34741c..5eaaca3f2 100644 --- a/acceptance-tests/tests/workflows/secretsFlow.spec.ts +++ b/acceptance-tests/tests/workflows/secretsFlow.spec.ts @@ -11,6 +11,20 @@ const SECRET = { value: 'an initial secret value', }; +const secretPollingOptions = { + interval: 5000, + timeout: 60000, +}; + +async function waitForSecretsListToContainSecret(testState: TestState) { + await expect + .poll( + () => testState.cli.executeWithTestConfig(['secrets', 'list']), + secretPollingOptions + ) + .toContain(SECRET.name); +} + describe('Secrets Flow', () => { let testState: TestState; @@ -29,22 +43,16 @@ describe('Secrets Flow', () => { ['secrets', 'add', SECRET.name], [SECRET.value, ENTER] ); - }); - }); - describe('hs secrets list', () => { - it('should list the secret', async () => { - await expect - .poll(() => testState.cli.executeWithTestConfig(['secrets', 'list']), { - interval: 1000, - timeout: 10000, - }) - .toContain(SECRET.name); + await waitForSecretsListToContainSecret(testState); }); }); describe('hs secrets update', () => { it('should update the existing secret', async () => { + // Wait for the secret to exist before updating it + await waitForSecretsListToContainSecret(testState); + await testState.cli.executeWithTestConfig( ['secrets', 'update', SECRET.name], ['a different secret value', ENTER] @@ -54,21 +62,19 @@ describe('Secrets Flow', () => { describe('hs secrets delete', () => { it('should delete the secret', async () => { - await testState.cli.executeWithTestConfig([ - 'secrets', - 'delete', - SECRET.name, - ]); - }); - }); + // Wait for the secret to exist before deleting it + await waitForSecretsListToContainSecret(testState); + + await testState.cli.executeWithTestConfig( + ['secrets', 'delete', SECRET.name], + ['Y', ENTER] + ); - describe('hs secrets list', () => { - it('should not list the secret', async () => { await expect - .poll(() => testState.cli.executeWithTestConfig(['secrets', 'list']), { - interval: 1000, - timeout: 10000, - }) + .poll( + () => testState.cli.executeWithTestConfig(['secrets', 'list']), + secretPollingOptions + ) .not.toContain(SECRET.name); }); }); diff --git a/bin/cli.js b/bin/cli.js index b5f620513..1644f7ed0 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -6,8 +6,19 @@ const chalk = require('chalk'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { addUserAgentHeader } = require('@hubspot/local-dev-lib/http'); +const { + loadConfig, + configFileExists, + getConfigPath, + validateConfig, +} = require('@hubspot/local-dev-lib/config'); const { logError } = require('../lib/errorHandlers/index'); -const { setLogLevel, getCommandName } = require('../lib/commonOpts'); +const { + setLogLevel, + getCommandName, + injectAccountIdMiddleware, +} = require('../lib/commonOpts'); +const { validateAccount } = require('../lib/validation'); const { trackHelpUsage, trackConvertFieldsUsage, @@ -17,6 +28,7 @@ const pkg = require('../package.json'); const { i18n } = require('../lib/lang'); const { EXIT_CODES } = require('../lib/enums/exitCodes'); const { UI_COLORS, uiCommandReference } = require('../lib/ui'); +const { checkAndWarnGitInclusion } = require('../lib/ui/git'); const removeCommand = require('../commands/remove'); const initCommand = require('../commands/init'); @@ -29,9 +41,9 @@ const uploadCommand = require('../commands/upload'); const createCommand = require('../commands/create'); const fetchCommand = require('../commands/fetch'); const filemanagerCommand = require('../commands/filemanager'); -const secretsCommand = require('../commands/secrets'); +const secretCommands = require('../commands/secret'); const customObjectCommand = require('../commands/customObject'); -const functionsCommand = require('../commands/functions'); +const functionCommands = require('../commands/function'); const listCommand = require('../commands/list'); const openCommand = require('../commands/open'); const mvCommand = require('../commands/mv'); @@ -39,11 +51,12 @@ const projectCommands = require('../commands/project'); const themeCommand = require('../commands/theme'); const moduleCommand = require('../commands/module'); const configCommand = require('../commands/config'); -const accountsCommand = require('../commands/accounts'); +const accountCommands = require('../commands/account'); const sandboxesCommand = require('../commands/sandbox'); const cmsCommand = require('../commands/cms'); const feedbackCommand = require('../commands/feedback'); const doctorCommand = require('../commands/doctor'); +const completionCommand = require('../commands/completion'); const notifier = updateNotifier({ pkg: { ...pkg, name: '@hubspot/cli' }, @@ -95,7 +108,7 @@ const handleFailure = (msg, err, yargs) => { } if (msg === null) { - yargs.showHelp(); + yargs.showHelp('log'); process.exit(EXIT_CODES.SUCCESS); } else { process.exit(EXIT_CODES.ERROR); @@ -135,17 +148,126 @@ const setRequestHeaders = () => { addUserAgentHeader('HubSpot CLI', pkg.version); }; +const isTargetedCommand = (options, commandMap) => { + const checkCommand = (options, commandMap) => { + const currentCommand = options._[0]; + + if (!commandMap[currentCommand]) { + return false; + } + + if (commandMap[currentCommand].target) { + return true; + } + + const subCommands = commandMap[currentCommand].subCommands || {}; + if (options._.length > 1) { + return checkCommand({ _: options._.slice(1) }, subCommands); + } + + return false; + }; + + return checkCommand(options, commandMap); +}; + +const SKIP_CONFIG_VALIDATION = { + init: { target: true }, + auth: { target: true }, +}; + +const loadConfigMiddleware = async options => { + // Skip this when no command is provided + if (!options._.length) { + return; + } + + const maybeValidateConfig = () => { + if ( + !isTargetedCommand(options, SKIP_CONFIG_VALIDATION) && + !validateConfig() + ) { + process.exit(EXIT_CODES.ERROR); + } + }; + + if (configFileExists(true) && options.config) { + logger.error( + i18n(`${i18nKey}.loadConfigMiddleware.configFileExists`, { + configPath: getConfigPath(), + }) + ); + process.exit(EXIT_CODES.ERROR); + } else if (!options._.includes('init')) { + const { config: configPath } = options; + loadConfig(configPath, options); + } + + maybeValidateConfig(); +}; + +const checkAndWarnGitInclusionMiddleware = options => { + // Skip this when no command is provided + if (!options._.length) { + return; + } + checkAndWarnGitInclusion(getConfigPath()); +}; + +const accountsSubCommands = { + target: false, + subCommands: { + clean: { target: true }, + list: { target: true }, + ls: { target: true }, + remove: { target: true }, + }, +}; +const sandboxesSubCommands = { + target: false, + subCommands: { + delete: { target: true }, + }, +}; + +const SKIP_ACCOUNT_VALIDATION = { + init: { target: true }, + auth: { target: true }, + account: accountsSubCommands, + accounts: accountsSubCommands, + sandbox: sandboxesSubCommands, + sandboxes: sandboxesSubCommands, +}; + +const validateAccountOptions = async options => { + // Skip this when no command is provided + if (!options._.length) { + return; + } + + let validAccount = true; + if (!isTargetedCommand(options, SKIP_ACCOUNT_VALIDATION)) { + validAccount = await validateAccount(options); + } + + if (!validAccount) { + process.exit(EXIT_CODES.ERROR); + } +}; + const argv = yargs .usage('The command line interface to interact with HubSpot.') - .middleware([setLogLevel, setRequestHeaders]) + // loadConfigMiddleware loads the new hidden config for all commands + .middleware([ + setLogLevel, + setRequestHeaders, + loadConfigMiddleware, + injectAccountIdMiddleware, + checkAndWarnGitInclusionMiddleware, + validateAccountOptions, + ]) .exitProcess(false) .fail(handleFailure) - .option('debug', { - alias: 'd', - default: false, - describe: 'Set log level to debug', - type: 'boolean', - }) .option('noHyperlinks', { default: false, describe: 'prevent hyperlinks from displaying in the ui', @@ -171,9 +293,9 @@ const argv = yargs .command(createCommand) .command(fetchCommand) .command(filemanagerCommand) - .command(secretsCommand) + .command(secretCommands) .command(customObjectCommand) - .command(functionsCommand) + .command(functionCommands) .command({ ...listCommand, aliases: 'ls', @@ -184,14 +306,15 @@ const argv = yargs .command(themeCommand) .command(moduleCommand) .command(configCommand) - .command(accountsCommand) + .command(accountCommands) .command(sandboxesCommand) .command(feedbackCommand) .command(doctorCommand) + .command(completionCommand) .help() + .alias('h', 'help') .recommendCommands() .demandCommand(1, '') - .completion() .wrap(getTerminalWidth()) .strict().argv; diff --git a/bin/hs b/bin/hs index 734f03b4b..060a8b7c5 100755 --- a/bin/hs +++ b/bin/hs @@ -1,3 +1,5 @@ #!/usr/bin/env node +require('./silenceErrors') + require('./cli'); diff --git a/bin/hscms b/bin/hscms index 734f03b4b..060a8b7c5 100755 --- a/bin/hscms +++ b/bin/hscms @@ -1,3 +1,5 @@ #!/usr/bin/env node +require('./silenceErrors') + require('./cli'); diff --git a/bin/silenceErrors.js b/bin/silenceErrors.js new file mode 100644 index 000000000..0d2119417 --- /dev/null +++ b/bin/silenceErrors.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +const SILENCED_ERRORS = ['DeprecationWarning:']; + +const originalConsoleError = console.error; + +console.error = msg => { + const isSilencedError = SILENCED_ERRORS.some( + error => typeof msg === 'string' && msg.includes(error) + ); + if (isSilencedError) { + return; + } + originalConsoleError(msg); +}; diff --git a/commands/__tests__/accounts.test.ts b/commands/__tests__/accounts.test.ts index 02756ea4a..8ea0f6bc6 100644 --- a/commands/__tests__/accounts.test.ts +++ b/commands/__tests__/accounts.test.ts @@ -1,43 +1,42 @@ // @ts-nocheck import yargs from 'yargs'; -import list from '../accounts/list'; -import rename from '../accounts/rename'; -import use from '../accounts/use'; -import info from '../accounts/info'; -import remove from '../accounts/remove'; -import clean from '../accounts/clean'; -import { addAccountOptions, addConfigOptions } from '../../lib/commonOpts'; +import list from '../account/list'; +import rename from '../account/rename'; +import use from '../account/use'; +import info from '../account/info'; +import remove from '../account/remove'; +import clean from '../account/clean'; jest.mock('yargs'); -jest.mock('../accounts/list'); -jest.mock('../accounts/rename'); -jest.mock('../accounts/use'); -jest.mock('../accounts/info'); -jest.mock('../accounts/remove'); -jest.mock('../accounts/clean'); +jest.mock('../account/list'); +jest.mock('../account/rename'); +jest.mock('../account/use'); +jest.mock('../account/info'); +jest.mock('../account/remove'); +jest.mock('../account/clean'); jest.mock('../../lib/commonOpts'); yargs.command.mockReturnValue(yargs); yargs.demandCommand.mockReturnValue(yargs); // Import this last so mocks apply -import accountsCommand from '../accounts'; +import accountCommands from '../account'; -describe('commands/accounts', () => { +describe('commands/account', () => { describe('command', () => { it('should have the correct command structure', () => { - expect(accountsCommand.command).toEqual('accounts'); + expect(accountCommands.command).toEqual(['account', 'accounts']); }); }); describe('describe', () => { it('should provide a description', () => { - expect(accountsCommand.describe).toBeDefined(); + expect(accountCommands.describe).toBeDefined(); }); }); describe('builder', () => { const subcommands = [ - ['list', { ...list, aliases: 'ls' }], + ['list', list], ['rename', rename], ['use', use], ['info', info], @@ -46,29 +45,19 @@ describe('commands/accounts', () => { ]; it('should demand the command takes one positional argument', () => { - accountsCommand.builder(yargs); + accountCommands.builder(yargs); expect(yargs.demandCommand).toHaveBeenCalledTimes(1); expect(yargs.demandCommand).toHaveBeenCalledWith(1, ''); }); - it('should support the correct options', () => { - accountsCommand.builder(yargs); - - expect(addConfigOptions).toHaveBeenCalledTimes(1); - expect(addConfigOptions).toHaveBeenCalledWith(yargs); - - expect(addAccountOptions).toHaveBeenCalledTimes(1); - expect(addAccountOptions).toHaveBeenCalledWith(yargs); - }); - it('should add the correct number of sub commands', () => { - accountsCommand.builder(yargs); + accountCommands.builder(yargs); expect(yargs.command).toHaveBeenCalledTimes(subcommands.length); }); it.each(subcommands)('should attach the %s subcommand', (name, module) => { - accountsCommand.builder(yargs); + accountCommands.builder(yargs); expect(yargs.command).toHaveBeenCalledWith(module); }); }); diff --git a/commands/__tests__/auth.test.ts b/commands/__tests__/auth.test.ts index 2a4e1bc6d..f744c2b24 100644 --- a/commands/__tests__/auth.test.ts +++ b/commands/__tests__/auth.test.ts @@ -11,7 +11,7 @@ import authCommand from '../auth'; describe('commands/auth', () => { describe('command', () => { it('should have the correct command structure', () => { - expect(authCommand.command).toEqual('auth [type] [--account]'); + expect(authCommand.command).toEqual('auth'); }); }); @@ -22,25 +22,16 @@ describe('commands/auth', () => { }); describe('builder', () => { - it('should support the correct positional arguments', () => { - authCommand.builder(yargs); - - expect(yargs.positional).toHaveBeenCalledTimes(1); - expect(yargs.positional).toHaveBeenCalledWith( - 'type', - expect.objectContaining({ - type: 'string', - choices: ['personalaccesskey', 'oauth2'], - default: 'personalaccesskey', - }) - ); - }); - it('should support the correct options', () => { authCommand.builder(yargs); expect(yargs.options).toHaveBeenCalledTimes(1); expect(yargs.options).toHaveBeenCalledWith({ + 'auth-type': expect.objectContaining({ + type: 'string', + choices: ['personalaccesskey', 'oauth2'], + default: 'personalaccesskey', + }), account: expect.objectContaining({ type: 'string' }), }); diff --git a/commands/__tests__/cms.test.ts b/commands/__tests__/cms.test.ts index f8117df34..eb707c221 100644 --- a/commands/__tests__/cms.test.ts +++ b/commands/__tests__/cms.test.ts @@ -2,13 +2,13 @@ import yargs from 'yargs'; import lighthouseScore from '../cms/lighthouseScore'; import convertFields from '../cms/convertFields'; -import reactModules from '../cms/reactModules'; +import getReactModule from '../cms/getReactModule'; import { addAccountOptions, addConfigOptions } from '../../lib/commonOpts'; jest.mock('yargs'); jest.mock('../cms/lighthouseScore'); jest.mock('../cms/convertFields'); -jest.mock('../cms/reactModules'); +jest.mock('../cms/getReactModule'); jest.mock('../../lib/commonOpts'); yargs.command.mockReturnValue(yargs); yargs.demandCommand.mockReturnValue(yargs); @@ -33,7 +33,7 @@ describe('commands/cms', () => { const subcommands = [ ['lighthouseScore', lighthouseScore], ['convertFields', convertFields], - ['reactModules', reactModules], + ['getReactModule', getReactModule], ]; it('should demand the command takes one positional argument', () => { diff --git a/commands/__tests__/config.test.ts b/commands/__tests__/config.test.ts index f7ceb64c7..a2b8fc34f 100644 --- a/commands/__tests__/config.test.ts +++ b/commands/__tests__/config.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck import yargs from 'yargs'; import set from '../config/set'; -import { addAccountOptions, addConfigOptions } from '../../lib/commonOpts'; +import { addConfigOptions } from '../../lib/commonOpts'; jest.mock('yargs'); jest.mock('../config/set'); @@ -40,9 +40,6 @@ describe('commands/config', () => { expect(addConfigOptions).toHaveBeenCalledTimes(1); expect(addConfigOptions).toHaveBeenCalledWith(yargs); - - expect(addAccountOptions).toHaveBeenCalledTimes(1); - expect(addAccountOptions).toHaveBeenCalledWith(yargs); }); it('should add the correct number of sub commands', () => { diff --git a/commands/__tests__/create.test.ts b/commands/__tests__/create.test.ts index bba3aab2c..d326ccbc8 100644 --- a/commands/__tests__/create.test.ts +++ b/commands/__tests__/create.test.ts @@ -9,9 +9,7 @@ import createCommand from '../create'; describe('commands/create', () => { describe('command', () => { it('should have the correct command structure', () => { - expect(createCommand.command).toEqual( - 'create [name] [dest] [--internal]' - ); + expect(createCommand.command).toEqual('create [name] [dest]'); }); }); @@ -43,7 +41,7 @@ describe('commands/create', () => { it('should support the correct options', () => { createCommand.builder(yargs); - expect(yargs.option).toHaveBeenCalledTimes(1); + expect(yargs.option).toHaveBeenCalledTimes(3); expect(yargs.option).toHaveBeenCalledWith( 'internal', expect.objectContaining({ type: 'boolean', hidden: true }) diff --git a/commands/__tests__/doctor.test.ts b/commands/__tests__/doctor.test.ts index bde5de907..d47b6c9aa 100644 --- a/commands/__tests__/doctor.test.ts +++ b/commands/__tests__/doctor.test.ts @@ -40,6 +40,7 @@ describe('doctor', () => { mockYargs = { option: jest.fn(() => mockYargs), + version: jest.fn(() => mockYargs), }; }); @@ -60,7 +61,7 @@ describe('doctor', () => { describe('builder', () => { it('should apply the correct options', () => { builder(mockYargs); - expect(mockYargs.option).toHaveBeenCalledTimes(1); + expect(mockYargs.option).toHaveBeenCalledTimes(2); expect(mockYargs.option).toHaveBeenCalledWith('output-dir', { describe: 'Directory to save a detailed diagnosis JSON file in', type: 'string', diff --git a/commands/__tests__/fetch.test.ts b/commands/__tests__/fetch.test.ts index 75a714255..45d893482 100644 --- a/commands/__tests__/fetch.test.ts +++ b/commands/__tests__/fetch.test.ts @@ -4,7 +4,7 @@ import { addAccountOptions, addConfigOptions, addOverwriteOptions, - addModeOptions, + addCmsPublishModeOptions, addUseEnvironmentOptions, } from '../../lib/commonOpts'; @@ -66,8 +66,10 @@ describe('commands/fetch', () => { expect(addOverwriteOptions).toHaveBeenCalledTimes(1); expect(addOverwriteOptions).toHaveBeenCalledWith(yargs); - expect(addModeOptions).toHaveBeenCalledTimes(1); - expect(addModeOptions).toHaveBeenCalledWith(yargs, { read: true }); + expect(addCmsPublishModeOptions).toHaveBeenCalledTimes(1); + expect(addCmsPublishModeOptions).toHaveBeenCalledWith(yargs, { + read: true, + }); expect(addUseEnvironmentOptions).toHaveBeenCalledTimes(1); expect(addUseEnvironmentOptions).toHaveBeenCalledWith(yargs); diff --git a/commands/__tests__/filemanager.test.ts b/commands/__tests__/filemanager.test.ts index 391aba08b..c358e90fd 100644 --- a/commands/__tests__/filemanager.test.ts +++ b/commands/__tests__/filemanager.test.ts @@ -2,11 +2,6 @@ import yargs from 'yargs'; import upload from '../filemanager/upload'; import fetch from '../filemanager/fetch'; -import { - addAccountOptions, - addConfigOptions, - addOverwriteOptions, -} from '../../lib/commonOpts'; jest.mock('yargs'); jest.mock('../filemanager/upload'); @@ -44,19 +39,6 @@ describe('commands/filemanager', () => { expect(yargs.demandCommand).toHaveBeenCalledWith(1, ''); }); - it('should support the correct options', () => { - filemanagerCommand.builder(yargs); - - expect(addConfigOptions).toHaveBeenCalledTimes(1); - expect(addConfigOptions).toHaveBeenCalledWith(yargs); - - expect(addAccountOptions).toHaveBeenCalledTimes(1); - expect(addAccountOptions).toHaveBeenCalledWith(yargs); - - expect(addOverwriteOptions).toHaveBeenCalledTimes(1); - expect(addOverwriteOptions).toHaveBeenCalledWith(yargs); - }); - it('should add the correct number of sub commands', () => { filemanagerCommand.builder(yargs); expect(yargs.command).toHaveBeenCalledTimes(subcommands.length); diff --git a/commands/__tests__/functions.test.ts b/commands/__tests__/function.test.ts similarity index 54% rename from commands/__tests__/functions.test.ts rename to commands/__tests__/function.test.ts index 93b772efe..25a009d50 100644 --- a/commands/__tests__/functions.test.ts +++ b/commands/__tests__/function.test.ts @@ -1,65 +1,58 @@ // @ts-nocheck import yargs from 'yargs'; -import list from '../functions/list'; -import deploy from '../functions/deploy'; -import server from '../functions/server'; -import { addAccountOptions, addConfigOptions } from '../../lib/commonOpts'; +import list from '../function/list'; +import deploy from '../function/deploy'; +import server from '../function/server'; jest.mock('yargs'); -jest.mock('../functions/list'); -jest.mock('../functions/deploy'); -jest.mock('../functions/server'); +jest.mock('../function/list'); +jest.mock('../function/deploy'); +jest.mock('../function/server'); jest.mock('../../lib/commonOpts'); yargs.command.mockReturnValue(yargs); yargs.demandCommand.mockReturnValue(yargs); // Import this last so mocks apply -import functionsCommand from '../functions'; +import functionCommands from '../function'; -describe('commands/functions', () => { +describe('commands/function', () => { describe('command', () => { it('should have the correct command structure', () => { - expect(functionsCommand.command).toEqual('functions'); + expect(functionCommands.command).toEqual(['function', 'functions']); }); }); describe('describe', () => { it('should provide a description', () => { - expect(functionsCommand.describe).toBeDefined(); + expect(functionCommands.describe).toBeDefined(); }); }); describe('builder', () => { const subcommands = [ - ['list', { ...list, aliases: 'ls' }], + ['list', list], ['deploy', deploy], ['server', server], ]; it('should demand the command takes one positional argument', () => { - functionsCommand.builder(yargs); + functionCommands.builder(yargs); expect(yargs.demandCommand).toHaveBeenCalledTimes(1); expect(yargs.demandCommand).toHaveBeenCalledWith(1, ''); }); it('should support the correct options', () => { - functionsCommand.builder(yargs); - - expect(addConfigOptions).toHaveBeenCalledTimes(1); - expect(addConfigOptions).toHaveBeenCalledWith(yargs); - - expect(addAccountOptions).toHaveBeenCalledTimes(1); - expect(addAccountOptions).toHaveBeenCalledWith(yargs); + functionCommands.builder(yargs); }); it('should add the correct number of sub commands', () => { - functionsCommand.builder(yargs); + functionCommands.builder(yargs); expect(yargs.command).toHaveBeenCalledTimes(subcommands.length); }); it.each(subcommands)('should attach the %s subcommand', (name, module) => { - functionsCommand.builder(yargs); + functionCommands.builder(yargs); expect(yargs.command).toHaveBeenCalledWith(module); }); }); diff --git a/commands/__tests__/init.test.ts b/commands/__tests__/init.test.ts index be0ebcacd..21538726b 100644 --- a/commands/__tests__/init.test.ts +++ b/commands/__tests__/init.test.ts @@ -9,9 +9,14 @@ jest.mock('../../lib/commonOpts'); import initCommand from '../init'; describe('commands/init', () => { + beforeEach(() => { + yargs.options = jest.fn().mockReturnThis(); + yargs.conflicts = jest.fn().mockReturnThis(); + }); + describe('command', () => { it('should have the correct command structure', () => { - expect(initCommand.command).toEqual('init [--account]'); + expect(initCommand.command).toEqual('init'); }); }); @@ -27,12 +32,13 @@ describe('commands/init', () => { expect(yargs.options).toHaveBeenCalledTimes(1); expect(yargs.options).toHaveBeenCalledWith({ - auth: expect.objectContaining({ + 'auth-type': expect.objectContaining({ type: 'string', choices: ['personalaccesskey', 'oauth2'], default: 'personalaccesskey', }), account: expect.objectContaining({ type: 'string' }), + 'use-hidden-config': expect.objectContaining({ type: 'boolean' }), 'disable-tracking': expect.objectContaining({ type: 'boolean', hidden: true, @@ -40,6 +46,12 @@ describe('commands/init', () => { }), }); + expect(yargs.conflicts).toHaveBeenCalledTimes(1); + expect(yargs.conflicts).toHaveBeenCalledWith( + 'use-hidden-config', + 'config' + ); + expect(addConfigOptions).toHaveBeenCalledTimes(1); expect(addConfigOptions).toHaveBeenCalledWith(yargs); diff --git a/commands/__tests__/logs.test.ts b/commands/__tests__/logs.test.ts index 7bac42a3d..b81f154a9 100644 --- a/commands/__tests__/logs.test.ts +++ b/commands/__tests__/logs.test.ts @@ -51,11 +51,10 @@ describe('commands/logs', () => { type: 'boolean', }), follow: expect.objectContaining({ - alias: ['t', 'tail', 'f'], + alias: ['f'], type: 'boolean', }), limit: expect.objectContaining({ - alias: ['limit', 'n', 'max-count'], type: 'number', }), }); @@ -72,9 +71,8 @@ describe('commands/logs', () => { it('should set the correct conflicts', () => { logsCommand.builder(yargs); - expect(yargs.conflicts).toHaveBeenCalledTimes(2); + expect(yargs.conflicts).toHaveBeenCalledTimes(1); expect(yargs.conflicts).toHaveBeenCalledWith('follow', 'limit'); - expect(yargs.conflicts).toHaveBeenCalledWith('functionName', 'endpoint'); }); it('should provide examples', () => { diff --git a/commands/__tests__/project.test.ts b/commands/__tests__/project.test.ts index 806feeebf..ea9249c63 100644 --- a/commands/__tests__/project.test.ts +++ b/commands/__tests__/project.test.ts @@ -13,7 +13,6 @@ import add from '../project/add'; import migrateApp from '../project/migrateApp'; import cloneApp from '../project/cloneApp'; import installDeps from '../project/installDeps'; -import { addConfigOptions, addAccountOptions } from '../../lib/commonOpts'; jest.mock('yargs'); jest.mock('../project/deploy'); @@ -39,7 +38,7 @@ import projectCommand from '../project'; describe('commands/project', () => { describe('command', () => { it('should have the correct command structure', () => { - expect(projectCommand.command).toEqual('project'); + expect(projectCommand.command).toEqual(['project', 'projects']); }); }); @@ -77,16 +76,6 @@ describe('commands/project', () => { expect(yargs.demandCommand).toHaveBeenCalledWith(1, ''); }); - it('should support the correct options', () => { - projectCommand.builder(yargs); - - expect(addConfigOptions).toHaveBeenCalledTimes(1); - expect(addConfigOptions).toHaveBeenCalledWith(yargs); - - expect(addAccountOptions).toHaveBeenCalledTimes(1); - expect(addAccountOptions).toHaveBeenCalledWith(yargs); - }); - it('should add the correct number of sub commands', () => { projectCommand.builder(yargs); expect(yargs.command).toHaveBeenCalledTimes(subcommands.length); diff --git a/commands/account.ts b/commands/account.ts new file mode 100644 index 000000000..42f9685ce --- /dev/null +++ b/commands/account.ts @@ -0,0 +1,29 @@ +// @ts-nocheck +const { addGlobalOptions } = require('../lib/commonOpts'); +const { i18n } = require('../lib/lang'); +const list = require('./account/list'); +const rename = require('./account/rename'); +const use = require('./account/use'); +const info = require('./account/info'); +const remove = require('./account/remove'); +const clean = require('./account/clean'); + +const i18nKey = 'commands.account'; + +exports.command = ['account', 'accounts']; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.builder = yargs => { + addGlobalOptions(yargs); + + yargs + .command(list) + .command(rename) + .command(use) + .command(info) + .command(remove) + .command(clean) + .demandCommand(1, ''); + + return yargs; +}; diff --git a/commands/accounts/clean.ts b/commands/account/clean.ts similarity index 82% rename from commands/accounts/clean.ts rename to commands/account/clean.ts index 02172373f..708179c07 100644 --- a/commands/accounts/clean.ts +++ b/commands/account/clean.ts @@ -6,35 +6,33 @@ const { const { trackCommandUsage } = require('../../lib/usageTracking'); const { i18n } = require('../../lib/lang'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); -const { - addConfigOptions, - addAccountOptions, - addUseEnvironmentOptions, - addTestingOptions, -} = require('../../lib/commonOpts'); +const { addTestingOptions, addConfigOptions } = require('../../lib/commonOpts'); const { promptUser } = require('../../lib/prompts/promptUtils'); const { getTableContents } = require('../../lib/ui/table'); const SpinniesManager = require('../../lib/ui/SpinniesManager'); -const { getConfig, deleteAccount } = require('@hubspot/local-dev-lib/config'); const { uiAccountDescription } = require('../../lib/ui'); +const { + deleteAccount, + getConfigAccounts, +} = require('@hubspot/local-dev-lib/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); -const i18nKey = 'commands.accounts.subcommands.clean'; +const i18nKey = 'commands.account.subcommands.clean'; exports.command = 'clean'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { const { qa } = options; - await loadAndValidateOptions(options, false); - - const config = getConfig(); trackCommandUsage('accounts-clean', null); - const filteredTestAccounts = config.portals.filter(p => + const accountsList = getConfigAccounts(); + const filteredTestAccounts = accountsList.filter(p => qa ? p.env === 'qa' : p.env !== 'qa' ); @@ -53,7 +51,10 @@ exports.handler = async options => { for (const account of filteredTestAccounts) { try { - await accessTokenForPersonalAccessKey(account.portalId, true); + await accessTokenForPersonalAccessKey( + getAccountIdentifier(account), + true + ); } catch (error) { if ( isSpecifiedError(error, { @@ -86,7 +87,9 @@ exports.handler = async options => { }); logger.log( getTableContents( - accountsToRemove.map(p => [uiAccountDescription(p.portalId)]), + accountsToRemove.map(p => [ + uiAccountDescription(getAccountIdentifier(p)), + ]), { border: { bodyLeft: ' ' } } ) ); @@ -127,8 +130,6 @@ exports.handler = async options => { exports.builder = yargs => { addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); addTestingOptions(yargs); yargs.example([['$0 accounts clean']]); diff --git a/commands/accounts/info.ts b/commands/account/info.ts similarity index 57% rename from commands/accounts/info.ts rename to commands/account/info.ts index a18bfe5fe..4dd0b5aa9 100644 --- a/commands/accounts/info.ts +++ b/commands/account/info.ts @@ -2,36 +2,32 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { getAccountConfig } = require('@hubspot/local-dev-lib/config'); const { getAccessToken } = require('@hubspot/local-dev-lib/personalAccessKey'); -const { - getAccountId, - addAccountOptions, - addConfigOptions, -} = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); +const { addConfigOptions } = require('../../lib/commonOpts'); const { i18n } = require('../../lib/lang'); const { getTableContents } = require('../../lib/ui/table'); -const i18nKey = 'commands.accounts.subcommands.info'; +const i18nKey = 'commands.account.subcommands.info'; exports.describe = i18n(`${i18nKey}.describe`); -exports.command = 'info [--account]'; +exports.command = 'info [account]'; exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - const config = getAccountConfig(accountId); - + const { derivedAccountId } = options; + const config = getAccountConfig(derivedAccountId); // check if the provided account is using a personal access key, if not, show an error - if (config.authType === 'personalaccesskey') { + if (config && config.authType === 'personalaccesskey') { const { name, personalAccessKey, env } = config; - const response = await getAccessToken(personalAccessKey, env, accountId); + const response = await getAccessToken( + personalAccessKey, + env, + derivedAccountId + ); const scopeGroups = response.scopeGroups.map(s => [s]); logger.log(i18n(`${i18nKey}.name`, { name })); - logger.log(i18n(`${i18nKey}.accountId`, { accountId })); + logger.log(i18n(`${i18nKey}.accountId`, { accountId: derivedAccountId })); logger.log(i18n(`${i18nKey}.scopeGroups`)); logger.log(getTableContents(scopeGroups, { border: { bodyLeft: ' ' } })); } else { @@ -41,15 +37,11 @@ exports.handler = async options => { exports.builder = yargs => { addConfigOptions(yargs); - addAccountOptions(yargs); yargs.example([ ['$0 accounts info', i18n(`${i18nKey}.examples.default`)], - [ - '$0 accounts info --account=MyAccount', - i18n(`${i18nKey}.examples.nameBased`), - ], - ['$0 accounts info --account=1234567', i18n(`${i18nKey}.examples.idBased`)], + ['$0 accounts info MyAccount', i18n(`${i18nKey}.examples.nameBased`)], + ['$0 accounts info 1234567', i18n(`${i18nKey}.examples.idBased`)], ]); return yargs; diff --git a/commands/accounts/list.ts b/commands/account/list.ts similarity index 73% rename from commands/accounts/list.ts rename to commands/account/list.ts index 99f067e06..d2a6cd4ec 100644 --- a/commands/accounts/list.ts +++ b/commands/account/list.ts @@ -1,15 +1,17 @@ // @ts-nocheck const { logger } = require('@hubspot/local-dev-lib/logger'); -const { getConfig, getConfigPath } = require('@hubspot/local-dev-lib/config'); +const { + getConfigPath, + getConfigDefaultAccount, + getConfigAccounts, +} = require('@hubspot/local-dev-lib/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); +const { addConfigOptions } = require('../../lib/commonOpts'); const { getTableContents, getTableHeader } = require('../../lib/ui/table'); -const { - addConfigOptions, - addAccountOptions, - getAccountId, -} = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { isSandbox, isDeveloperTestAccount } = require('../../lib/accountTypes'); const { i18n } = require('../../lib/lang'); @@ -18,9 +20,9 @@ const { HUBSPOT_ACCOUNT_TYPE_STRINGS, } = require('@hubspot/local-dev-lib/constants/config'); -const i18nKey = 'commands.accounts.subcommands.list'; +const i18nKey = 'commands.account.subcommands.list'; -exports.command = 'list'; +exports.command = ['list', 'ls']; exports.describe = i18n(`${i18nKey}.describe`); const sortAndMapPortals = portals => { @@ -34,7 +36,7 @@ const sortAndMapPortals = portals => { p.accountType === HUBSPOT_ACCOUNT_TYPES.APP_DEVELOPER) ) .forEach(portal => { - mappedPortalData[portal.portalId] = [portal]; + mappedPortalData[getAccountIdentifier(portal)] = [portal]; }); // Non-standard portals (sandbox, developer test account) portals @@ -46,7 +48,7 @@ const sortAndMapPortals = portals => { p, ]; } else { - mappedPortalData[p.portalId] = [p]; + mappedPortalData[getAccountIdentifier(p)] = [p]; } }); return mappedPortalData; @@ -56,7 +58,7 @@ const getPortalData = mappedPortalData => { const portalData = []; Object.entries(mappedPortalData).forEach(([key, set]) => { const hasParentPortal = set.filter( - p => p.portalId === parseInt(key, 10) + p => getAccountIdentifier(p) === parseInt(key, 10) )[0]; set.forEach(portal => { let name = `${portal.name} [${ @@ -71,22 +73,20 @@ const getPortalData = mappedPortalData => { name = `↳ ${name}`; } } - portalData.push([name, portal.portalId, portal.authType]); + portalData.push([name, getAccountIdentifier(portal), portal.authType]); }); }); return portalData; }; exports.handler = async options => { - await loadAndValidateOptions(options, false); + const { derivedAccountId } = options; - const accountId = getAccountId(options); + trackCommandUsage('accounts-list', null, derivedAccountId); - trackCommandUsage('accounts-list', null, accountId); - - const config = getConfig(); const configPath = getConfigPath(); - const mappedPortalData = sortAndMapPortals(config.portals); + const accountsList = getConfigAccounts(); + const mappedPortalData = sortAndMapPortals(accountsList); const portalData = getPortalData(mappedPortalData); portalData.unshift( getTableHeader([ @@ -98,7 +98,9 @@ exports.handler = async options => { logger.log(i18n(`${i18nKey}.configPath`, { configPath })); logger.log( - i18n(`${i18nKey}.defaultAccount`, { account: config.defaultPortal }) + i18n(`${i18nKey}.defaultAccount`, { + account: getConfigDefaultAccount(), + }) ); logger.log(i18n(`${i18nKey}.accounts`)); logger.log(getTableContents(portalData, { border: { bodyLeft: ' ' } })); @@ -106,9 +108,6 @@ exports.handler = async options => { exports.builder = yargs => { addConfigOptions(yargs); - addAccountOptions(yargs); - yargs.example([['$0 accounts list']]); - return yargs; }; diff --git a/commands/accounts/remove.ts b/commands/account/remove.ts similarity index 76% rename from commands/accounts/remove.ts rename to commands/account/remove.ts index a0345de03..b0a11533f 100644 --- a/commands/accounts/remove.ts +++ b/commands/account/remove.ts @@ -1,7 +1,8 @@ // @ts-nocheck +const { addConfigOptions } = require('../../lib/commonOpts'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { - getConfig, + loadConfig, getConfigPath, deleteAccount, getConfigDefaultAccount, @@ -12,19 +13,15 @@ const { const { trackCommandUsage } = require('../../lib/usageTracking'); const { i18n } = require('../../lib/lang'); const { selectAccountFromConfig } = require('../../lib/prompts/accountsPrompt'); -const { loadAndValidateOptions } = require('../../lib/validation'); -const i18nKey = 'commands.accounts.subcommands.remove'; +const i18nKey = 'commands.account.subcommands.remove'; -exports.command = 'remove [--account]'; +exports.command = 'remove [account]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options, false); - - let config = getConfig(); - - let accountToRemove = options.account; + const { account } = options; + let accountToRemove = account; if (accountToRemove && !getAccountIdFromConfig(accountToRemove)) { logger.error( @@ -37,7 +34,6 @@ exports.handler = async options => { if (!accountToRemove || !getAccountIdFromConfig(accountToRemove)) { accountToRemove = await selectAccountFromConfig( - config, i18n(`${i18nKey}.prompts.selectAccountToRemove`) ); } @@ -58,27 +54,25 @@ exports.handler = async options => { ); // Get updated version of the config - config = getConfig(); + loadConfig(getConfigPath(), options); if (accountToRemove === currentDefaultAccount) { logger.log(); logger.log(i18n(`${i18nKey}.logs.replaceDefaultAccount`)); - const newDefaultAccount = await selectAccountFromConfig(config); + const newDefaultAccount = await selectAccountFromConfig(); updateDefaultAccount(newDefaultAccount); } }; exports.builder = yargs => { - yargs.option('account', { + addConfigOptions(yargs); + yargs.positional('account', { describe: i18n(`${i18nKey}.options.account.describe`), type: 'string', }); yargs.example([ ['$0 accounts remove', i18n(`${i18nKey}.examples.default`)], - [ - '$0 accounts remove --account=MyAccount', - i18n(`${i18nKey}.examples.byName`), - ], + ['$0 accounts remove MyAccount', i18n(`${i18nKey}.examples.byName`)], ]); return yargs; diff --git a/commands/accounts/rename.ts b/commands/account/rename.ts similarity index 71% rename from commands/accounts/rename.ts rename to commands/account/rename.ts index 266c191d4..ca94e5d47 100644 --- a/commands/accounts/rename.ts +++ b/commands/account/rename.ts @@ -2,27 +2,19 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { renameAccount } = require('@hubspot/local-dev-lib/config'); -const { - addConfigOptions, - addAccountOptions, - getAccountId, -} = require('../../lib/commonOpts'); +const { addConfigOptions, addAccountOptions } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.accounts.subcommands.rename'; +const i18nKey = 'commands.account.subcommands.rename'; exports.command = 'rename '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - loadAndValidateOptions(options); + const { accountName, newName, derivedAccountId } = options; - const { accountName, newName } = options; - const accountId = getAccountId(options); - - trackCommandUsage('accounts-rename', null, accountId); + trackCommandUsage('accounts-rename', null, derivedAccountId); await renameAccount(accountName, newName); diff --git a/commands/accounts/use.ts b/commands/account/use.ts similarity index 68% rename from commands/accounts/use.ts rename to commands/account/use.ts index 02ff9e92d..df2ec0594 100644 --- a/commands/accounts/use.ts +++ b/commands/account/use.ts @@ -1,7 +1,6 @@ // @ts-nocheck const { logger } = require('@hubspot/local-dev-lib/logger'); const { - getConfig, getConfigPath, updateDefaultAccount, getAccountId: getAccountIdFromConfig, @@ -10,22 +9,17 @@ const { const { trackCommandUsage } = require('../../lib/usageTracking'); const { i18n } = require('../../lib/lang'); const { selectAccountFromConfig } = require('../../lib/prompts/accountsPrompt'); -const { loadAndValidateOptions } = require('../../lib/validation'); -const i18nKey = 'commands.accounts.subcommands.use'; +const i18nKey = 'commands.account.subcommands.use'; -exports.command = 'use [--account]'; +exports.command = 'use [account]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options, false); - - const config = getConfig(); - let newDefaultAccount = options.account; if (!newDefaultAccount) { - newDefaultAccount = await selectAccountFromConfig(config); + newDefaultAccount = await selectAccountFromConfig(); } else if (!getAccountIdFromConfig(newDefaultAccount)) { logger.error( i18n(`${i18nKey}.errors.accountNotFound`, { @@ -33,7 +27,7 @@ exports.handler = async options => { configPath: getConfigPath(), }) ); - newDefaultAccount = await selectAccountFromConfig(config); + newDefaultAccount = await selectAccountFromConfig(); } trackCommandUsage( @@ -52,17 +46,14 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.option('account', { + yargs.positional('account', { describe: i18n(`${i18nKey}.options.account.describe`), type: 'string', }); yargs.example([ ['$0 accounts use', i18n(`${i18nKey}.examples.default`)], - [ - '$0 accounts use --account=MyAccount', - i18n(`${i18nKey}.examples.nameBased`), - ], - ['$0 accounts use --account=1234567', i18n(`${i18nKey}.examples.idBased`)], + ['$0 accounts use MyAccount', i18n(`${i18nKey}.examples.nameBased`)], + ['$0 accounts use 1234567', i18n(`${i18nKey}.examples.idBased`)], ]); return yargs; diff --git a/commands/accounts.ts b/commands/accounts.ts deleted file mode 100644 index fd567bf97..000000000 --- a/commands/accounts.ts +++ /dev/null @@ -1,33 +0,0 @@ -// @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); -const { i18n } = require('../lib/lang'); -const list = require('./accounts/list'); -const rename = require('./accounts/rename'); -const use = require('./accounts/use'); -const info = require('./accounts/info'); -const remove = require('./accounts/remove'); -const clean = require('./accounts/clean'); - -const i18nKey = 'commands.accounts'; - -exports.command = 'accounts'; -exports.describe = i18n(`${i18nKey}.describe`); - -exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - - yargs - .command({ - ...list, - aliases: 'ls', - }) - .command(rename) - .command(use) - .command(info) - .command(remove) - .command(clean) - .demandCommand(1, ''); - - return yargs; -}; diff --git a/commands/auth.ts b/commands/auth.ts index ab553dd30..b0d26e1d5 100644 --- a/commands/auth.ts +++ b/commands/auth.ts @@ -19,9 +19,9 @@ const { const { updateAccountConfig, writeConfig, - getConfig, getConfigPath, loadConfig, + getConfigDefaultAccount, } = require('@hubspot/local-dev-lib/config'); const { commaSeparatedValues, @@ -41,6 +41,7 @@ const { setLogLevel, getAccountId, addTestingOptions, + addGlobalOptions, } = require('../lib/commonOpts'); const { trackAuthAction, trackCommandUsage } = require('../lib/usageTracking'); const { authenticateWithOauth } = require('../lib/oauth'); @@ -60,30 +61,36 @@ const ALLOWED_AUTH_METHODS = [ OAUTH_AUTH_METHOD.value, PERSONAL_ACCESS_KEY_AUTH_METHOD.value, ]; -const SUPPORTED_AUTHENTICATION_PROTOCOLS_TEXT = commaSeparatedValues( - ALLOWED_AUTH_METHODS -); +const SUPPORTED_AUTHENTICATION_PROTOCOLS_TEXT = + commaSeparatedValues(ALLOWED_AUTH_METHODS); -exports.command = 'auth [type] [--account]'; +exports.command = 'auth'; exports.describe = i18n(`${i18nKey}.describe`, { supportedProtocols: SUPPORTED_AUTHENTICATION_PROTOCOLS_TEXT, }); exports.handler = async options => { - const { type, config: c, qa, account } = options; + const { + authType: authTypeFlagValue, + config: configFlagValue, + qa, + providedAccountId, + } = options; const authType = - (type && type.toLowerCase()) || PERSONAL_ACCESS_KEY_AUTH_METHOD.value; + (authTypeFlagValue && authTypeFlagValue.toLowerCase()) || + PERSONAL_ACCESS_KEY_AUTH_METHOD.value; setLogLevel(options); - if (!getConfigPath(c)) { + const env = qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD; + // Needed to load deprecated config + loadConfig(configFlagValue); + checkAndWarnGitInclusion(getConfigPath()); + + if (!getConfigPath(configFlagValue)) { logger.error(i18n(`${i18nKey}.errors.noConfigFileFound`)); process.exit(EXIT_CODES.ERROR); } - const env = qa ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD; - loadConfig(c); - checkAndWarnGitInclusion(getConfigPath()); - trackCommandUsage('auth'); trackAuthAction('auth', authType, TRACKING_STATUS.STARTED); @@ -104,7 +111,10 @@ exports.handler = async options => { successAuthMethod = OAUTH_AUTH_METHOD.name; break; case PERSONAL_ACCESS_KEY_AUTH_METHOD.value: - configData = await personalAccessKeyPrompt({ env, account }); + configData = await personalAccessKeyPrompt({ + env, + account: providedAccountId, + }); try { token = await getAccessToken(configData.personalAccessKey, env); @@ -168,10 +178,9 @@ exports.handler = async options => { }) ); } else { - const config = getConfig(); logger.info( i18n(`lib.prompts.setAsDefaultAccountPrompt.keepingCurrentDefault`, { - accountName: config.defaultPortal, + accountName: getConfigDefaultAccount(), }) ); } @@ -195,28 +204,32 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.positional('type', { - describe: i18n(`${i18nKey}.positionals.type.describe`), - type: 'string', - choices: [ - `${PERSONAL_ACCESS_KEY_AUTH_METHOD.value}`, - `${OAUTH_AUTH_METHOD.value}`, - ], - default: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, - defaultDescription: i18n(`${i18nKey}.positionals.type.defaultDescription`, { - authMethod: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, - }), - }); - yargs.options({ + 'auth-type': { + describe: i18n(`${i18nKey}.options.authType.describe`), + type: 'string', + choices: [ + `${PERSONAL_ACCESS_KEY_AUTH_METHOD.value}`, + `${OAUTH_AUTH_METHOD.value}`, + ], + default: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, + defaultDescription: i18n( + `${i18nKey}.options.authType.defaultDescription`, + { + authMethod: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, + } + ), + }, account: { describe: i18n(`${i18nKey}.options.account.describe`), type: 'string', + alias: 'a', }, }); addConfigOptions(yargs); addTestingOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/cms.ts b/commands/cms.ts index 824fe0d5d..0e6622d35 100644 --- a/commands/cms.ts +++ b/commands/cms.ts @@ -1,9 +1,13 @@ // @ts-nocheck const { i18n } = require('../lib/lang'); -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { + addConfigOptions, + addAccountOptions, + addGlobalOptions, +} = require('../lib/commonOpts'); const lighthouseScore = require('./cms/lighthouseScore'); const convertFields = require('./cms/convertFields'); -const reactModules = require('./cms/reactModules'); +const getReactModule = require('./cms/getReactModule'); const i18nKey = 'commands.cms'; @@ -13,11 +17,12 @@ exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); + addGlobalOptions(yargs); yargs .command(lighthouseScore) .command(convertFields) - .command(reactModules) + .command(getReactModule) .demandCommand(1, ''); return yargs; diff --git a/commands/cms/getReactModule.ts b/commands/cms/getReactModule.ts new file mode 100644 index 000000000..ae5f16511 --- /dev/null +++ b/commands/cms/getReactModule.ts @@ -0,0 +1,84 @@ +// @ts-nocheck +const fs = require('fs'); +const path = require('path'); +const { getCwd } = require('@hubspot/local-dev-lib/path'); +const { logger } = require('@hubspot/local-dev-lib/logger'); +const { retrieveDefaultModule } = require('@hubspot/local-dev-lib/cms/modules'); +const { i18n } = require('../../lib/lang'); +const { logError } = require('../../lib/errorHandlers/index'); +const { trackCommandUsage } = require('../../lib/usageTracking'); +const { listPrompt } = require('../../lib/prompts/promptUtils'); +const { EXIT_CODES } = require('../../lib/enums/exitCodes'); + +const i18nKey = 'commands.cms.subcommands.getReactModule'; + +exports.command = 'get-react-module [name] [dest]'; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.handler = async options => { + const { name, dest } = options; + + trackCommandUsage('get-react-modules'); + + let moduleToRetrieve = name; + + if (!moduleToRetrieve) { + let availableModules; + try { + availableModules = await retrieveDefaultModule(null, ''); + } catch (e) { + logError(e); + } + + const moduleChoice = await listPrompt( + i18n(`${i18nKey}.selectModulePrompt`), + { + choices: availableModules.map(module => module.name), + } + ); + moduleToRetrieve = moduleChoice; + } + + const destPath = dest + ? path.join(path.resolve(getCwd(), dest), `${moduleToRetrieve}`) + : path.join(getCwd(), `${moduleToRetrieve}`); + + if (fs.existsSync(destPath)) { + logger.error( + i18n(`${i18nKey}.errors.pathExists`, { + path: destPath, + }) + ); + return; + } + + try { + await retrieveDefaultModule(moduleToRetrieve, destPath); + + logger.success( + i18n(`${i18nKey}.success.moduleDownloaded`, { + moduleName: moduleToRetrieve, + path: destPath, + }) + ); + } catch (e) { + if (e.cause && e.cause.code === 'ERR_BAD_REQUEST') { + logger.error(i18n(`${i18nKey}.errors.invalidName`)); + } else { + logError(e); + } + } + process.exit(EXIT_CODES.SUCCESS); +}; + +exports.builder = yargs => { + yargs.positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }); + yargs.positional('dest', { + describe: i18n(`${i18nKey}.positionals.dest.describe`), + type: 'string', + }); + return yargs; +}; diff --git a/commands/cms/lighthouseScore.ts b/commands/cms/lighthouseScore.ts index 75a3caa0e..7f4bc4a37 100644 --- a/commands/cms/lighthouseScore.ts +++ b/commands/cms/lighthouseScore.ts @@ -4,11 +4,9 @@ const { addAccountOptions, addConfigOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { getTableContents, getTableHeader } = require('../../lib/ui/table'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { promptUser } = require('../../lib/prompts/promptUtils'); const { i18n } = require('../../lib/lang'); const { fetchThemes } = require('@hubspot/local-dev-lib/api/designManager'); @@ -68,17 +66,16 @@ const selectTheme = async accountId => { }; exports.handler = async options => { - await loadAndValidateOptions(options); - const accountId = getAccountId(options); + const { target, verbose, theme, derivedAccountId } = options; - const includeDesktopScore = options.target === 'desktop' || !options.verbose; - const includeMobileScore = options.target === 'mobile' || !options.verbose; - let themeToCheck = options.theme; + const includeDesktopScore = target === 'desktop' || !verbose; + const includeMobileScore = target === 'mobile' || !verbose; + let themeToCheck = theme; if (themeToCheck) { let isValidTheme = true; try { - const { data: result } = await fetchThemes(accountId, { + const { data: result } = await fetchThemes(derivedAccountId, { name: encodeURIComponent(themeToCheck), }); isValidTheme = result && result.total; @@ -92,14 +89,14 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } } else { - themeToCheck = await selectTheme(accountId); + themeToCheck = await selectTheme(derivedAccountId); logger.log(); } // Kick off the scoring let requestResult; try { - const { data } = await requestLighthouseScore(accountId, { + const { data } = await requestLighthouseScore(derivedAccountId, { themePath: themeToCheck, }); requestResult = data; @@ -123,7 +120,7 @@ exports.handler = async options => { const checkScoreStatus = async () => { let desktopScoreStatus = 'COMPLETED'; if (includeDesktopScore) { - const { data } = await getLighthouseScoreStatus(accountId, { + const { data } = await getLighthouseScoreStatus(derivedAccountId, { themeId: requestResult.desktopId, }); desktopScoreStatus = data; @@ -131,7 +128,7 @@ exports.handler = async options => { let mobileScoreStatus = 'COMPLETED'; if (includeDesktopScore) { - const { data } = await getLighthouseScoreStatus(accountId, { + const { data } = await getLighthouseScoreStatus(derivedAccountId, { themeId: requestResult.mobileId, }); mobileScoreStatus = data; @@ -159,10 +156,10 @@ exports.handler = async options => { let mobileScoreResult = {}; let verboseViewAverageScoreResult = {}; try { - const params = { isAverage: !options.verbose }; + const params = { isAverage: !verbose }; if (includeDesktopScore) { - const { data } = await getLighthouseScore(accountId, { + const { data } = await getLighthouseScore(derivedAccountId, { ...params, desktopId: requestResult.desktopId, }); @@ -170,15 +167,15 @@ exports.handler = async options => { } if (includeMobileScore) { - const { data } = await getLighthouseScore(accountId, { + const { data } = await getLighthouseScore(derivedAccountId, { ...params, mobileId: requestResult.mobileId, }); mobileScoreResult = data; } // This is needed to show the average scores above the verbose output - if (options.verbose) { - const { data } = await getLighthouseScore(accountId, { + if (verbose) { + const { data } = await getLighthouseScore(derivedAccountId, { ...params, isAverage: true, desktopId: includeDesktopScore ? requestResult.desktopId : null, @@ -191,8 +188,8 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } - if (options.verbose) { - logger.log(`${themeToCheck} ${options.target} scores`); + if (verbose) { + logger.log(`${themeToCheck} ${target} scores`); const tableHeader = getTableHeader(DEFAULT_TABLE_HEADER); @@ -221,7 +218,7 @@ exports.handler = async options => { ]); const scoreResult = - options.target === 'desktop' ? desktopScoreResult : mobileScoreResult; + target === 'desktop' ? desktopScoreResult : mobileScoreResult; const templateTableData = scoreResult.scores.map(score => { return [ @@ -255,9 +252,7 @@ exports.handler = async options => { } logger.log(); - logger.info( - i18n(`${i18nKey}.info.targetDeviceNote`, { target: options.target }) - ); + logger.info(i18n(`${i18nKey}.info.targetDeviceNote`, { target })); } else { logger.log(`Theme: ${themeToCheck}`); const tableHeader = getTableHeader(['Target', ...DEFAULT_TABLE_HEADER]); diff --git a/commands/cms/reactModules.ts b/commands/cms/reactModules.ts deleted file mode 100644 index efc408011..000000000 --- a/commands/cms/reactModules.ts +++ /dev/null @@ -1,68 +0,0 @@ -// @ts-nocheck -const fs = require('fs'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { retrieveDefaultModule } = require('@hubspot/local-dev-lib/cms/modules'); -const { i18n } = require('../../lib/lang'); -const path = require('path'); -const { trackCommandUsage } = require('../../lib/usageTracking'); - -const i18nKey = 'commands.cms.subcommands.reactModule'; - -exports.command = 'get-react-module [--name] [--dest]'; -exports.describe = i18n(`${i18nKey}.describe`); - -exports.handler = async options => { - const { name, dest } = options; - - trackCommandUsage('get-react-modules'); - - const destPath = dest - ? path.join(dest, `${name}`) - : path.join(process.cwd(), `${name}`); - - if (fs.existsSync(destPath)) { - logger.error( - i18n(`${i18nKey}.errors.pathExists`, { - path: destPath, - }) - ); - return; - } - - try { - const modules = await retrieveDefaultModule(name, destPath); - - if (!name) { - logger.group(i18n(`${i18nKey}.groupLabel`)); - modules.forEach(module => { - logger.log(module.name); - }); - logger.groupEnd(i18n(`${i18nKey}.groupLabel`)); - } else { - logger.success( - i18n(`${i18nKey}.success.moduleDownloaded`, { - moduleName: name, - path: destPath, - }) - ); - } - } catch (e) { - if (e.cause && e.cause.code === 'ERR_BAD_REQUEST') { - logger.error(i18n(`${i18nKey}.errors.invalidName`)); - } else { - logger.error(e); - } - } -}; - -exports.builder = yargs => { - yargs.option('name', { - describe: i18n(`${i18nKey}.options.name.describe`), - type: 'string', - }); - yargs.option('dest', { - describe: i18n(`${i18nKey}.options.dest.describe`), - type: 'string', - }); - return yargs; -}; diff --git a/commands/completion.ts b/commands/completion.ts new file mode 100644 index 000000000..37a54d690 --- /dev/null +++ b/commands/completion.ts @@ -0,0 +1,26 @@ +// @ts-nocheck +const yargsParser = require('yargs-parser'); +const { i18n } = require('../lib/lang'); +const { trackCommandUsage } = require('../lib/usageTracking'); + +const i18nKey = 'commands.completion'; + +exports.command = 'completion'; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.handler = async () => { + await trackCommandUsage('completion'); +}; + +exports.builder = yargs => { + const { help } = yargsParser(process.argv.slice(2)); + + if (!help) { + yargs.completion(); + } + + yargs.example([ + ['$0 completion >> ~/.zshrc', i18n(`${i18nKey}.examples.default`)], + ]); + return yargs; +}; diff --git a/commands/config.ts b/commands/config.ts index 2defc6cbb..b7b5dddb4 100644 --- a/commands/config.ts +++ b/commands/config.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { addConfigOptions, addGlobalOptions } = require('../lib/commonOpts'); const { i18n } = require('../lib/lang'); const set = require('./config/set'); @@ -10,7 +10,7 @@ exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { addConfigOptions(yargs); - addAccountOptions(yargs); + addGlobalOptions(yargs); yargs.command(set).demandCommand(1, ''); diff --git a/commands/config/set.ts b/commands/config/set.ts index 03625c43a..75e6053e3 100644 --- a/commands/config/set.ts +++ b/commands/config/set.ts @@ -1,12 +1,10 @@ // @ts-nocheck -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); -const { getAccountId } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { promptUser } = require('../../lib/prompts/promptUtils'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { - setDefaultMode, + setDefaultCmsPublishMode, setHttpTimeout, setAllowUsageTracking, } = require('../../lib/configOptions'); @@ -17,29 +15,32 @@ exports.command = 'set'; exports.describe = i18n(`${i18nKey}.describe`); const selectOptions = async () => { - const { mode } = await promptUser([ + const { cmsPublishMode } = await promptUser([ { type: 'list', look: false, - name: 'mode', + name: 'cmsPublishMode', pageSize: 20, message: i18n(`${i18nKey}.promptMessage`), choices: [ - { name: 'Default mode', value: { defaultMode: '' } }, + { + name: 'Default CMS publish mode', + value: { defaultCmsPublishMode: '' }, + }, { name: 'Allow usage tracking', value: { allowUsageTracking: '' } }, { name: 'HTTP timeout', value: { httpTimeout: '' } }, ], }, ]); - return mode; + return cmsPublishMode; }; const handleConfigUpdate = async (accountId, options) => { - const { allowUsageTracking, defaultMode, httpTimeout } = options; + const { allowUsageTracking, defaultCmsPublishMode, httpTimeout } = options; - if (typeof defaultMode !== 'undefined') { - await setDefaultMode({ defaultMode, accountId }); + if (typeof defaultCmsPublishMode !== 'undefined') { + await setDefaultCmsPublishMode({ defaultCmsPublishMode, accountId }); return true; } else if (typeof httpTimeout !== 'undefined') { await setHttpTimeout({ httpTimeout, accountId }); @@ -53,18 +54,16 @@ const handleConfigUpdate = async (accountId, options) => { }; exports.handler = async options => { - await loadAndValidateOptions(options); + const { derivedAccountId } = options; - const accountId = getAccountId(options); + trackCommandUsage('config-set', null, derivedAccountId); - trackCommandUsage('config-set', null, accountId); - - const configUpdated = await handleConfigUpdate(accountId, options); + const configUpdated = await handleConfigUpdate(derivedAccountId, options); if (!configUpdated) { const selectedOptions = await selectOptions(); - await handleConfigUpdate(accountId, selectedOptions); + await handleConfigUpdate(derivedAccountId, selectedOptions); } process.exit(EXIT_CODES.SUCCESS); @@ -73,27 +72,23 @@ exports.handler = async options => { exports.builder = yargs => { yargs .options({ - defaultMode: { + 'default-cms-publish-mode': { describe: i18n(`${i18nKey}.options.defaultMode.describe`), type: 'string', }, - allowUsageTracking: { + 'allow-usage-tracking': { describe: i18n(`${i18nKey}.options.allowUsageTracking.describe`), type: 'boolean', }, - httpTimeout: { + 'http-timeout': { describe: i18n(`${i18nKey}.options.httpTimeout.describe`), type: 'string', }, }) - .conflicts('defaultMode', 'allowUsageTracking') - .conflicts('defaultMode', 'httpTimeout') - .conflicts('allowUsageTracking', 'httpTimeout'); - - yargs.example([['$0 config set', i18n(`${i18nKey}.examples.default`)]]); - - //TODO remove this when "hs accounts use" is fully rolled out - yargs.strict(false); + .conflicts('defaultCmsPublishMode', 'allowUsageTracking') + .conflicts('defaultCmsPublishMode', 'httpTimeout') + .conflicts('allowUsageTracking', 'httpTimeout') + .example([['$0 config set', i18n(`${i18nKey}.examples.default`)]]); return yargs; }; diff --git a/commands/create.ts b/commands/create.ts index 22ac9c649..5dd1ef57f 100644 --- a/commands/create.ts +++ b/commands/create.ts @@ -26,7 +26,11 @@ const fs = require('fs-extra'); const { logError } = require('../lib/errorHandlers/index'); const { logger } = require('@hubspot/local-dev-lib/logger'); -const { setLogLevel, getAccountId } = require('../lib/commonOpts'); +const { + setLogLevel, + addGlobalOptions, + addConfigOptions, +} = require('../lib/commonOpts'); const { resolveLocalPath } = require('../lib/filesystem'); const { trackCommandUsage } = require('../lib/usageTracking'); const assets = require('./create/index'); @@ -38,7 +42,7 @@ const SUPPORTED_ASSET_TYPES = Object.keys(assets) .filter(t => !assets[t].hidden) .join(', '); -exports.command = 'create [name] [dest] [--internal]'; +exports.command = 'create [name] [dest]'; exports.describe = i18n(`${i18nKey}.describe`, { supportedAssetTypes: SUPPORTED_ASSET_TYPES, }); @@ -75,7 +79,8 @@ exports.handler = async options => { const argsToPass = { assetType, name, dest, getInternalVersion, options }; dest = argsToPass.dest = resolveLocalPath(asset.dest(argsToPass)); - trackCommandUsage('create', { assetType }, getAccountId(options)); + const { derivedAccountId } = options; + trackCommandUsage('create', { assetType }, derivedAccountId); try { await fs.ensureDir(dest); @@ -116,5 +121,8 @@ exports.builder = yargs => { hidden: true, }); + addConfigOptions(yargs); + addGlobalOptions(yargs); + return yargs; }; diff --git a/commands/customObject.ts b/commands/customObject.ts index 29cc776d4..870f4fca0 100644 --- a/commands/customObject.ts +++ b/commands/customObject.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { addGlobalOptions } = require('../lib/commonOpts'); const schemaCommand = require('./customObject/schema'); const createCommand = require('./customObject/create'); const { i18n } = require('../lib/lang'); @@ -8,7 +8,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 = () => { @@ -23,8 +23,7 @@ const logBetaMessage = () => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); + addGlobalOptions(yargs); yargs .middleware([logBetaMessage]) diff --git a/commands/customObject/create.ts b/commands/customObject/create.ts index 41e166153..2e483dfde 100644 --- a/commands/customObject/create.ts +++ b/commands/customObject/create.ts @@ -1,13 +1,11 @@ // @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'); -const { - checkAndConvertToJson, - loadAndValidateOptions, -} = require('../../lib/validation'); +const { checkAndConvertToJson } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { getAccountId } = require('../../lib/commonOpts'); const { batchCreateObjects, } = require('@hubspot/local-dev-lib/api/customObjects'); @@ -16,46 +14,50 @@ 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 } = options; - - await loadAndValidateOptions(options); + const { path, name: providedName, derivedAccountId } = options; + let definitionPath = path; - const accountId = getAccountId(options); + trackCommandUsage('custom-object-batch-create', null, derivedAccountId); - trackCommandUsage('custom-object-batch-create', null, accountId); + if (!definitionPath) { + definitionPath = await inputPrompt(i18n(`${i18nKey}.inputPath`)); + } - const filePath = getAbsoluteFilePath(definition); + 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(accountId, name, objectJson); + await batchCreateObjects(derivedAccountId, name, objectJson); logger.success(i18n(`${i18nKey}.success.objectsCreated`)); } catch (e) { - logError(e, { accountId }); + 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 7f42b5c52..9037fe59b 100644 --- a/commands/customObject/schema/create.ts +++ b/commands/customObject/schema/create.ts @@ -2,12 +2,9 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../../lib/errorHandlers/index'); const { getAbsoluteFilePath } = require('@hubspot/local-dev-lib/path'); -const { - checkAndConvertToJson, - loadAndValidateOptions, -} = require('../../../lib/validation'); +const { checkAndConvertToJson } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); -const { addTestingOptions, getAccountId } = require('../../../lib/commonOpts'); +const { addTestingOptions } = require('../../../lib/commonOpts'); const { getEnv, isConfigFlagEnabled, @@ -28,19 +25,15 @@ 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 } = options; - - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { path, derivedAccountId } = options; - trackCommandUsage('custom-object-schema-create', null, accountId); + 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); @@ -48,27 +41,27 @@ exports.handler = async options => { try { if (isConfigFlagEnabled(CONFIG_FLAGS.USE_CUSTOM_OBJECT_HUBFILE)) { - await createSchemaFromHubFile(accountId, filePath); + await createSchemaFromHubFile(derivedAccountId, filePath); logger.success( i18n(`${i18nKey}.success.schemaCreated`, { - accountId, + accountId: derivedAccountId, }) ); } else { - const { data } = await createObjectSchema(accountId, schemaJson); + const { data } = await createObjectSchema(derivedAccountId, schemaJson); logger.success( i18n(`${i18nKey}.success.schemaViewable`, { url: `${getHubSpotWebsiteOrigin( getEnv() === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD - )}/contacts/${accountId}/objects/${data.objectTypeId}`, + )}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`, }) ); } } catch (e) { - logError(e, { accountId }); + logError(e, { accountId: derivedAccountId }); logger.error( i18n(`${i18nKey}.errors.creationFailed`, { - definition, + definition: path, }) ); } @@ -77,8 +70,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 13bf5506e..38fd9e738 100644 --- a/commands/customObject/schema/delete.ts +++ b/commands/customObject/schema/delete.ts @@ -1,8 +1,10 @@ // @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'); -const { getAccountId } = require('../../../lib/commonOpts'); const { deleteObjectSchema, } = require('@hubspot/local-dev-lib/api/customObjects'); @@ -11,20 +13,36 @@ 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 } = options; + const { name: providedName, force, derivedAccountId } = options; + + trackCommandUsage('custom-object-schema-delete', null, derivedAccountId); - await loadAndValidateOptions(options); + 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 accountId = getAccountId(options); + const shouldDelete = + force || + (await confirmPrompt(i18n(`${i18nKey}.confirmDelete`, { name }))); - trackCommandUsage('custom-object-schema-delete', null, accountId); + if (!shouldDelete) { + logger.info(i18n(`${i18nKey}.deleteCancelled`, { name })); + return process.exit(EXIT_CODES.SUCCESS); + } - try { - await deleteObjectSchema(accountId, name); + await deleteObjectSchema(derivedAccountId, name); logger.success( i18n(`${i18nKey}.success.delete`, { name, @@ -41,12 +59,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 361767ab4..1a05a8af6 100644 --- a/commands/customObject/schema/fetch-all.ts +++ b/commands/customObject/schema/fetch-all.ts @@ -1,8 +1,8 @@ // @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'); -const { getAccountId } = require('../../../lib/commonOpts'); const { downloadSchemas, getResolvedPath, @@ -17,18 +17,18 @@ exports.command = 'fetch-all [dest]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { derivedAccountId, dest: providedDest } = options; - trackCommandUsage('custom-object-schema-fetch-all', null, accountId); + trackCommandUsage('custom-object-schema-fetch-all', null, derivedAccountId); try { - const schemas = await downloadSchemas(accountId, 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) { @@ -38,16 +38,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 13c5f1c7f..f32df58fe 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'); @@ -10,30 +13,39 @@ const { const { fetchSchema } = require('@hubspot/local-dev-lib/api/fileTransport'); const { getCwd } = require('@hubspot/local-dev-lib/path'); -const { loadAndValidateOptions } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); -const { getAccountId } = require('../../../lib/commonOpts'); const { i18n } = require('../../../lib/lang'); 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 } = options; + const { name: providedName, dest: providedDest, derivedAccountId } = options; - await loadAndValidateOptions(options); + trackCommandUsage('custom-object-schema-fetch', null, derivedAccountId); + let name; - const accountId = getAccountId(options); + try { + const { + data: { results }, + } = await fetchObjectSchemas(derivedAccountId); + const schemaNames = results?.map(({ name: schemaName }) => schemaName); - trackCommandUsage('custom-object-schema-fetch', null, accountId); + name = + providedName || + (await listPrompt(i18n(`${i18nKey}.selectSchema`), { + choices: schemaNames, + })); + + const dest = + providedDest || (await inputPrompt(i18n(`${i18nKey}.inputDest`))); - try { if (isConfigFlagEnabled(CONFIG_FLAGS.USE_CUSTOM_OBJECT_HUBFILE)) { const fullpath = path.resolve(getCwd(), dest); - await fetchSchema(accountId, name, fullpath); + await fetchSchema(derivedAccountId, name, fullpath); logger.success( i18n(`${i18nKey}.success.save`, { name, @@ -41,7 +53,7 @@ exports.handler = async options => { }) ); } else { - await downloadSchema(accountId, name, dest); + await downloadSchema(derivedAccountId, name, dest); logger.success( i18n(`${i18nKey}.success.savedToPath`, { path: getResolvedPath(dest, name), @@ -59,24 +71,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/list.ts b/commands/customObject/schema/list.ts index 285e5ebba..8f0c91d74 100644 --- a/commands/customObject/schema/list.ts +++ b/commands/customObject/schema/list.ts @@ -2,9 +2,7 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../../lib/errorHandlers/index'); -const { loadAndValidateOptions } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); -const { getAccountId } = require('../../../lib/commonOpts'); const { listSchemas } = require('../../../lib/schema'); const { i18n } = require('../../../lib/lang'); @@ -14,14 +12,12 @@ exports.command = 'list'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options); + const { derivedAccountId } = options; - const accountId = getAccountId(options); - - trackCommandUsage('custom-object-schema-list', null, accountId); + trackCommandUsage('custom-object-schema-list', null, derivedAccountId); try { - await listSchemas(accountId); + await listSchemas(derivedAccountId); } catch (e) { logError(e); logger.error(i18n(`${i18nKey}.errors.list`)); diff --git a/commands/customObject/schema/update.ts b/commands/customObject/schema/update.ts index 4736459ec..09951e8df 100644 --- a/commands/customObject/schema/update.ts +++ b/commands/customObject/schema/update.ts @@ -1,16 +1,16 @@ // @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'); const { ENVIRONMENTS, } = require('@hubspot/local-dev-lib/constants/environments'); -const { - checkAndConvertToJson, - loadAndValidateOptions, -} = require('../../../lib/validation'); +const { checkAndConvertToJson } = require('../../../lib/validation'); const { trackCommandUsage } = require('../../../lib/usageTracking'); -const { addTestingOptions, getAccountId } = require('../../../lib/commonOpts'); +const { addTestingOptions } = require('../../../lib/commonOpts'); const { CONFIG_FLAGS } = require('../../../lib/constants'); const { getEnv, @@ -28,47 +28,58 @@ 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 } = options; - - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { path, name: providedName, derivedAccountId } = options; - trackCommandUsage('custom-object-schema-update', null, accountId); + 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(accountId, filePath); + await updateSchemaFromHubFile(derivedAccountId, filePath); logger.success( i18n(`${i18nKey}.success.update`, { - accountId, + accountId: derivedAccountId, }) ); } else { - const { data } = await updateObjectSchema(accountId, name, schemaJson); + const { data } = await updateObjectSchema( + derivedAccountId, + name, + schemaJson + ); logger.success( i18n(`${i18nKey}.success.viewAtUrl`, { url: `${getHubSpotWebsiteOrigin( getEnv() === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD - )}/contacts/${accountId}/objects/${data.objectTypeId}`, + )}/contacts/${derivedAccountId}/objects/${data.objectTypeId}`, }) ); } } catch (e) { - logError(e, { accountId }); + logError(e, { accountId: derivedAccountId }); logger.error( i18n(`${i18nKey}.errors.update`, { - definition, + definition: path, }) ); } @@ -77,13 +88,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/commands/doctor.ts b/commands/doctor.ts index 8d03f3b46..546110dc1 100644 --- a/commands/doctor.ts +++ b/commands/doctor.ts @@ -9,6 +9,7 @@ import { EXIT_CODES } from '../lib/enums/exitCodes'; import path from 'path'; import { ArgumentsCamelCase, BuilderCallback, Options } from 'yargs'; import { getCwd } from '@hubspot/local-dev-lib/path'; +import { addGlobalOptions } from '../lib/commonOpts'; const { i18n } = require('../lib/lang'); export interface DoctorOptions { @@ -25,7 +26,7 @@ export const handler = async ({ }: ArgumentsCamelCase) => { const doctor = new Doctor(); - trackCommandUsage(command, undefined, doctor.accountId); + trackCommandUsage(command, undefined, doctor.accountId || undefined); const output = await doctor.diagnose(); @@ -33,8 +34,8 @@ export const handler = async ({ if (totalCount > 0) { trackCommandMetadataUsage( command, - { success: false, type: totalCount }, - doctor.accountId + { successful: false, type: totalCount }, + doctor.accountId || undefined ); } @@ -78,4 +79,5 @@ export const builder: BuilderCallback = yargs => { describe: i18n(`${i18nKey}.options.outputDir`), type: 'string', }); + addGlobalOptions(yargs); }; diff --git a/commands/feedback.ts b/commands/feedback.ts index fa3f763d6..8f1806757 100644 --- a/commands/feedback.ts +++ b/commands/feedback.ts @@ -4,6 +4,7 @@ const open = require('open'); const { i18n } = require('../lib/lang'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { confirmPrompt, listPrompt } = require('../lib/prompts/promptUtils'); +const { addGlobalOptions } = require('../lib/commonOpts'); const i18nKey = 'commands.project.subcommands.feedback'; @@ -54,4 +55,6 @@ exports.builder = yargs => { type: 'boolean', }, }); + + addGlobalOptions(yargs); }; diff --git a/commands/fetch.ts b/commands/fetch.ts index f9e08fd31..6285dbe5a 100644 --- a/commands/fetch.ts +++ b/commands/fetch.ts @@ -5,13 +5,13 @@ const { addConfigOptions, addAccountOptions, addOverwriteOptions, - addModeOptions, + addCmsPublishModeOptions, addUseEnvironmentOptions, - getAccountId, - getMode, + getCmsPublishMode, + addGlobalOptions, } = require('../lib/commonOpts'); const { resolveLocalPath } = require('../lib/filesystem'); -const { validateMode, loadAndValidateOptions } = require('../lib/validation'); +const { validateCmsPublishMode } = require('../lib/validation'); const { trackCommandUsage } = require('../lib/usageTracking'); const { i18n } = require('../lib/lang'); @@ -25,9 +25,7 @@ exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { const { src, dest } = options; - await loadAndValidateOptions(options); - - if (!validateMode(options)) { + if (!validateCmsPublishMode(options)) { process.exit(EXIT_CODES.ERROR); } @@ -36,18 +34,18 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } - const accountId = getAccountId(options); - const mode = getMode(options); + const { derivedAccountId } = options; + const cmsPublishMode = getCmsPublishMode(options); - trackCommandUsage('fetch', { mode }, accountId); + trackCommandUsage('fetch', { mode: cmsPublishMode }, derivedAccountId); try { // Fetch and write file/folder. await downloadFileOrFolder( - accountId, + derivedAccountId, src, resolveLocalPath(dest), - mode, + cmsPublishMode, options ); } catch (err) { @@ -57,12 +55,6 @@ exports.handler = async options => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addOverwriteOptions(yargs); - addModeOptions(yargs, { read: true }); - addUseEnvironmentOptions(yargs); - yargs.positional('src', { describe: i18n(`${i18nKey}.positionals.src.describe`), type: 'string', @@ -89,5 +81,12 @@ exports.builder = yargs => { }, }); + addConfigOptions(yargs); + addAccountOptions(yargs); + addOverwriteOptions(yargs); + addCmsPublishModeOptions(yargs, { read: true }); + addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); + return yargs; }; diff --git a/commands/filemanager.ts b/commands/filemanager.ts index f09d0c123..078f4d464 100644 --- a/commands/filemanager.ts +++ b/commands/filemanager.ts @@ -1,9 +1,4 @@ // @ts-nocheck -const { - addConfigOptions, - addAccountOptions, - addOverwriteOptions, -} = require('../lib/commonOpts'); const upload = require('./filemanager/upload'); const fetch = require('./filemanager/fetch'); const { i18n } = require('../lib/lang'); @@ -14,14 +9,7 @@ exports.command = 'filemanager'; exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { - addOverwriteOptions(yargs); - addConfigOptions(yargs); - addAccountOptions(yargs); - - yargs - .command(upload) - .command(fetch) - .demandCommand(1, ''); + yargs.command(upload).command(fetch).demandCommand(1, ''); return yargs; }; diff --git a/commands/filemanager/fetch.ts b/commands/filemanager/fetch.ts index 695ec58fc..57d6098e8 100644 --- a/commands/filemanager/fetch.ts +++ b/commands/filemanager/fetch.ts @@ -5,10 +5,10 @@ const { resolveLocalPath } = require('../../lib/filesystem'); const { addConfigOptions, addAccountOptions, + addOverwriteOptions, + addGlobalOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { i18n } = require('../../lib/lang'); @@ -20,9 +20,7 @@ exports.command = 'fetch [dest]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { src, includeArchived } = options; - - await loadAndValidateOptions(options); + const { src, includeArchived, derivedAccountId, overwrite } = options; if (typeof src !== 'string') { logger.error(i18n(`${i18nKey}.errors.sourceRequired`)); @@ -31,17 +29,15 @@ exports.handler = async options => { const dest = resolveLocalPath(options.dest); - const accountId = getAccountId(options); - - trackCommandUsage('filemanager-fetch', null, accountId); + trackCommandUsage('filemanager-fetch', null, derivedAccountId); try { // Fetch and write file/folder. await downloadFileOrFolder( - accountId, + derivedAccountId, src, dest, - false, + overwrite, includeArchived || false ); } catch (err) { @@ -51,8 +47,10 @@ exports.handler = async options => { }; exports.builder = yargs => { + addGlobalOptions(yargs); addConfigOptions(yargs); addAccountOptions(yargs); + addOverwriteOptions(yargs); addUseEnvironmentOptions(yargs); yargs.positional('src', { diff --git a/commands/filemanager/upload.ts b/commands/filemanager/upload.ts index 985758232..6ca6b0081 100644 --- a/commands/filemanager/upload.ts +++ b/commands/filemanager/upload.ts @@ -14,11 +14,10 @@ const { shouldIgnoreFile } = require('@hubspot/local-dev-lib/ignoreRules'); const { ApiErrorContext, logError } = require('../../lib/errorHandlers/index'); const { addConfigOptions, + addGlobalOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { i18n } = require('../../lib/lang'); @@ -29,11 +28,8 @@ exports.command = 'upload '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { src, dest } = options; + const { src, dest, derivedAccountId } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); const absoluteSrcPath = path.resolve(getCwd(), src); let stats; @@ -64,7 +60,7 @@ exports.handler = async options => { trackCommandUsage( 'filemanager-upload', { type: stats.isFile() ? 'file' : 'folder' }, - accountId + derivedAccountId ); const srcDestIssues = await validateSrcAndDestPaths( @@ -86,11 +82,11 @@ exports.handler = async options => { return; } - uploadFile(accountId, absoluteSrcPath, normalizedDest) + uploadFile(derivedAccountId, absoluteSrcPath, normalizedDest) .then(() => { logger.success( i18n(`${i18nKey}.success.upload`, { - accountId, + accountId: derivedAccountId, dest: normalizedDest, src, }) @@ -106,7 +102,7 @@ exports.handler = async options => { logError( error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: normalizedDest, payload: src, }) @@ -115,12 +111,12 @@ exports.handler = async options => { } else { logger.log( i18n(`${i18nKey}.logs.uploading`, { - accountId, + accountId: derivedAccountId, dest, src, }) ); - uploadFolder(accountId, absoluteSrcPath, dest) + uploadFolder(derivedAccountId, absoluteSrcPath, dest) .then(() => { logger.success( i18n(`${i18nKey}.success.uploadComplete`, { @@ -131,13 +127,14 @@ exports.handler = async options => { .catch(error => { logger.error(i18n(`${i18nKey}.errors.uploadingFailed`)); logError(error, { - accountId, + accountId: derivedAccountId, }); }); } }; exports.builder = yargs => { + addGlobalOptions(yargs); addConfigOptions(yargs); addAccountOptions(yargs); addUseEnvironmentOptions(yargs); diff --git a/commands/function.ts b/commands/function.ts new file mode 100644 index 000000000..3105398e3 --- /dev/null +++ b/commands/function.ts @@ -0,0 +1,18 @@ +// @ts-nocheck +const { addGlobalOptions } = require('../lib/commonOpts'); +const list = require('./function/list'); +const deploy = require('./function/deploy'); +const server = require('./function/server'); +const { i18n } = require('../lib/lang'); + +const i18nKey = 'commands.function'; + +exports.command = ['function', 'functions']; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.builder = yargs => { + addGlobalOptions(yargs); + yargs.command(list).command(deploy).command(server).demandCommand(1, ''); + + return yargs; +}; diff --git a/commands/functions/deploy.ts b/commands/function/deploy.ts similarity index 79% rename from commands/functions/deploy.ts rename to commands/function/deploy.ts index 76e2e930f..11c2f1f92 100644 --- a/commands/functions/deploy.ts +++ b/commands/function/deploy.ts @@ -3,7 +3,6 @@ const SpinniesManager = require('../../lib/ui/SpinniesManager'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); @@ -15,24 +14,20 @@ const { buildPackage, getBuildStatus, } = require('@hubspot/local-dev-lib/api/functions'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { outputBuildLog } = require('../../lib/serverlessLogs'); const { i18n } = require('../../lib/lang'); const { isHubSpotHttpError } = require('@hubspot/local-dev-lib/errors/index'); -const i18nKey = 'commands.functions.subcommands.deploy'; +const i18nKey = 'commands.function.subcommands.deploy'; exports.command = 'deploy '; exports.describe = false; exports.handler = async options => { - await loadAndValidateOptions(options); - - const { path: functionPath } = options; - const accountId = getAccountId(options); + const { path: functionPath, derivedAccountId } = options; const splitFunctionPath = functionPath.split('.'); - trackCommandUsage('functions-deploy', null, accountId); + trackCommandUsage('functions-deploy', null, derivedAccountId); if ( !splitFunctionPath.length || @@ -56,14 +51,17 @@ exports.handler = async options => { SpinniesManager.add('loading', { text: i18n(`${i18nKey}.loading`, { - account: uiAccountDescription(accountId), + account: uiAccountDescription(derivedAccountId), functionPath, }), }); try { - const { data: buildId } = await buildPackage(accountId, functionPath); - const successResp = await poll(getBuildStatus, accountId, buildId); + const { data: buildId } = await buildPackage( + derivedAccountId, + functionPath + ); + const successResp = await poll(getBuildStatus, derivedAccountId, buildId); const buildTimeSeconds = (successResp.buildTime / 1000).toFixed(2); SpinniesManager.succeed('loading'); @@ -71,7 +69,7 @@ exports.handler = async options => { await outputBuildLog(successResp.cdnUrl); logger.success( i18n(`${i18nKey}.success.deployed`, { - accountId, + accountId: derivedAccountId, buildTimeSeconds, functionPath, }) @@ -79,7 +77,7 @@ exports.handler = async options => { } catch (e) { SpinniesManager.fail('loading', { text: i18n(`${i18nKey}.loadingFailed`, { - account: uiAccountDescription(accountId), + account: uiAccountDescription(derivedAccountId), functionPath, }), }); @@ -98,7 +96,13 @@ exports.handler = async options => { }) ); } else { - logError(e, new ApiErrorContext({ accountId, request: functionPath })); + logError( + e, + new ApiErrorContext({ + accountId: derivedAccountId, + request: functionPath, + }) + ); } } }; diff --git a/commands/functions/list.ts b/commands/function/list.ts similarity index 77% rename from commands/functions/list.ts rename to commands/function/list.ts index 430d028f7..80be65a0b 100644 --- a/commands/functions/list.ts +++ b/commands/function/list.ts @@ -8,31 +8,29 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.functions.subcommands.list'; +const i18nKey = 'commands.function.subcommands.list'; const { EXIT_CODES } = require('../../lib/enums/exitCodes'); -exports.command = 'list'; +exports.command = ['list', 'ls']; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - loadAndValidateOptions(options); + const { derivedAccountId } = options; - const accountId = getAccountId(options); - - trackCommandUsage('functions-list', null, accountId); + trackCommandUsage('functions-list', null, derivedAccountId); logger.debug(i18n(`${i18nKey}.debug.gettingFunctions`)); - const { data: routesResp } = await getRoutes(accountId).catch(async e => { - logError(e, new ApiErrorContext({ accountId })); - process.exit(EXIT_CODES.SUCCESS); - }); + const { data: routesResp } = await getRoutes(derivedAccountId).catch( + async e => { + logError(e, new ApiErrorContext({ accountId: derivedAccountId })); + process.exit(EXIT_CODES.SUCCESS); + } + ); if (!routesResp.objects.length) { return logger.info(i18n(`${i18nKey}.info.noFunctions`)); diff --git a/commands/functions/server.ts b/commands/function/server.ts similarity index 82% rename from commands/functions/server.ts rename to commands/function/server.ts index a77ea587e..bcb4709c9 100644 --- a/commands/functions/server.ts +++ b/commands/function/server.ts @@ -2,27 +2,22 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { start: startTestServer } = require('@hubspot/serverless-dev-runtime'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.functions.subcommands.server'; +const i18nKey = 'commands.function.subcommands.server'; exports.command = 'server '; exports.describe = false; exports.handler = async options => { - await loadAndValidateOptions(options); + const { path: functionPath, derivedAccountId } = options; - const { path: functionPath } = options; - const accountId = getAccountId(options); - - trackCommandUsage('functions-server', null, accountId); + trackCommandUsage('functions-server', null, derivedAccountId); logger.debug( i18n(`${i18nKey}.debug.startingServer`, { @@ -31,7 +26,7 @@ exports.handler = async options => { ); startTestServer({ - accountId, + accountId: derivedAccountId, ...options, }); }; diff --git a/commands/functions.ts b/commands/functions.ts deleted file mode 100644 index 7931fd7e9..000000000 --- a/commands/functions.ts +++ /dev/null @@ -1,27 +0,0 @@ -// @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); -const list = require('./functions/list'); -const deploy = require('./functions/deploy'); -const server = require('./functions/server'); -const { i18n } = require('../lib/lang'); - -const i18nKey = 'commands.functions'; - -exports.command = 'functions'; -exports.describe = i18n(`${i18nKey}.describe`); - -exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - - yargs - .command({ - ...list, - aliases: 'ls', - }) - .command(deploy) - .command(server) - .demandCommand(1, ''); - - return yargs; -}; diff --git a/commands/hubdb.ts b/commands/hubdb.ts index 556faa03a..c51d75270 100644 --- a/commands/hubdb.ts +++ b/commands/hubdb.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { addGlobalOptions } = require('../lib/commonOpts'); const createCommand = require('./hubdb/create'); const fetchCommand = require('./hubdb/fetch'); const deleteCommand = require('./hubdb/delete'); @@ -12,8 +12,7 @@ exports.command = 'hubdb'; exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); + addGlobalOptions(yargs); yargs .command(clearCommand) diff --git a/commands/hubdb/clear.ts b/commands/hubdb/clear.ts index 7ceec6a59..8894f8804 100644 --- a/commands/hubdb/clear.ts +++ b/commands/hubdb/clear.ts @@ -3,34 +3,40 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../lib/errorHandlers/index'); const { clearHubDbTableRows } = require('@hubspot/local-dev-lib/hubdb'); const { publishTable } = require('@hubspot/local-dev-lib/api/hubdb'); - -const { loadAndValidateOptions } = require('../../lib/validation'); +const { + selectHubDBTablePrompt, +} = require('../../lib/prompts/selectHubDBTablePrompt'); const { trackCommandUsage } = require('../../lib/usageTracking'); - const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.hubdb.subcommands.clear'; -exports.command = 'clear '; +exports.command = 'clear [table-id]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { tableId } = options; + const { derivedAccountId } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - - trackCommandUsage('hubdb-clear', null, accountId); + trackCommandUsage('hubdb-clear', null, derivedAccountId); try { - const { deletedRowCount } = await clearHubDbTableRows(accountId, tableId); + const { tableId } = + 'tableId' in options + ? options + : await selectHubDBTablePrompt({ + accountId: derivedAccountId, + options, + }); + + const { deletedRowCount } = await clearHubDbTableRows( + derivedAccountId, + tableId + ); if (deletedRowCount > 0) { logger.log( i18n(`${i18nKey}.logs.removedRows`, { @@ -40,7 +46,7 @@ exports.handler = async options => { ); const { data: { rowCount }, - } = await publishTable(accountId, tableId); + } = await publishTable(derivedAccountId, tableId); logger.log( i18n(`${i18nKey}.logs.rowCount`, { rowCount, @@ -64,7 +70,7 @@ exports.builder = yargs => { addConfigOptions(yargs); addUseEnvironmentOptions(yargs); - yargs.positional('tableId', { + yargs.positional('table-id', { describe: i18n(`${i18nKey}.positionals.tableId.describe`), type: 'string', }); diff --git a/commands/hubdb/create.ts b/commands/hubdb/create.ts index b25afd906..a712bcc1e 100644 --- a/commands/hubdb/create.ts +++ b/commands/hubdb/create.ts @@ -5,48 +5,67 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../lib/errorHandlers/index'); const { getCwd } = require('@hubspot/local-dev-lib/path'); const { createHubDbTable } = require('@hubspot/local-dev-lib/hubdb'); - -const { - checkAndConvertToJson, - loadAndValidateOptions, -} = require('../../lib/validation'); +const { untildify, isValidPath } = require('@hubspot/local-dev-lib/path'); +const { promptUser } = require('../../lib/prompts/promptUtils'); +const { checkAndConvertToJson } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.hubdb.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 { src } = options; +function selectPathPrompt(options) { + return promptUser([ + { + name: 'path', + message: i18n(`${i18nKey}.enterPath`), + when: !options.path, + validate: (input: string) => { + if (!input) { + return i18n(`${i18nKey}.errors.pathRequired`); + } + if (!isValidPath(input)) { + return i18n(`${i18nKey}.errors.invalidCharacters`); + } + return true; + }, + filter: (input: string) => { + return untildify(input); + }, + }, + ]); +} - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); +exports.handler = async options => { + const { derivedAccountId } = options; - trackCommandUsage('hubdb-create', null, accountId); + trackCommandUsage('hubdb-create', null, derivedAccountId); + let filePath; try { - const filePath = path.resolve(getCwd(), src); + const filePath = + 'path' in options + ? path.resolve(getCwd(), options.path) + : path.resolve(getCwd(), (await selectPathPrompt(options)).path); if (!checkAndConvertToJson(filePath)) { process.exit(EXIT_CODES.ERROR); } const table = await createHubDbTable( - accountId, - path.resolve(getCwd(), src) + derivedAccountId, + path.resolve(getCwd(), filePath) ); logger.success( i18n(`${i18nKey}.success.create`, { - accountId, + accountId: derivedAccountId, rowCount: table.rowCount, tableId: table.tableId, }) @@ -54,7 +73,7 @@ exports.handler = async options => { } catch (e) { logger.error( i18n(`${i18nKey}.errors.create`, { - src, + filePath, }) ); logError(e); @@ -66,8 +85,8 @@ exports.builder = yargs => { addConfigOptions(yargs); addUseEnvironmentOptions(yargs); - yargs.positional('src', { - describe: i18n(`${i18nKey}.positionals.src.describe`), + yargs.options('path', { + describe: i18n(`${i18nKey}.options.path.describe`), type: 'string', }); }; diff --git a/commands/hubdb/delete.ts b/commands/hubdb/delete.ts index f102256ef..c183ec73c 100644 --- a/commands/hubdb/delete.ts +++ b/commands/hubdb/delete.ts @@ -2,39 +2,58 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../lib/errorHandlers/index'); const { deleteTable } = require('@hubspot/local-dev-lib/api/hubdb'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); - const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); +const { + selectHubDBTablePrompt, +} = require('../../lib/prompts/selectHubDBTablePrompt'); +const { promptUser } = require('../../lib/prompts/promptUtils'); +const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.hubdb.subcommands.delete'; -exports.command = 'delete '; +exports.command = 'delete [table-id]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { tableId } = options; + const { force, derivedAccountId } = options; - await loadAndValidateOptions(options); + trackCommandUsage('hubdb-delete', null, derivedAccountId); - const accountId = getAccountId(options); + try { + const { tableId } = + 'tableId' in options + ? options + : await selectHubDBTablePrompt({ + accountId: derivedAccountId, + options, + }); - trackCommandUsage('hubdb-delete', null, accountId); + if (!force) { + const { shouldDeleteTable } = await promptUser({ + name: 'shouldDeleteTable', + type: 'confirm', + message: i18n(`${i18nKey}.shouldDeleteTable`, { tableId }), + }); - try { - await deleteTable(accountId, tableId); + if (!shouldDeleteTable) { + process.exit(EXIT_CODES.SUCCESS); + } + } + + await deleteTable(derivedAccountId, tableId); logger.success( i18n(`${i18nKey}.success.delete`, { - accountId, + accountId: derivedAccountId, tableId, }) ); + process.exit(EXIT_CODES.SUCCESS); } catch (e) { logger.error( i18n(`${i18nKey}.errors.delete`, { @@ -50,8 +69,13 @@ exports.builder = yargs => { addConfigOptions(yargs); addUseEnvironmentOptions(yargs); - yargs.positional('tableId', { + yargs.positional('table-id', { describe: i18n(`${i18nKey}.positionals.tableId.describe`), type: 'string', }); + + yargs.option('force', { + describe: i18n(`${i18nKey}.options.force.describe`), + type: 'boolean', + }); }; diff --git a/commands/hubdb/fetch.ts b/commands/hubdb/fetch.ts index e8d8cdd09..94e0cbda0 100644 --- a/commands/hubdb/fetch.ts +++ b/commands/hubdb/fetch.ts @@ -2,34 +2,42 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError } = require('../../lib/errorHandlers/index'); const { downloadHubDbTable } = require('@hubspot/local-dev-lib/hubdb'); - -const { loadAndValidateOptions } = require('../../lib/validation'); +const { + selectHubDBTablePrompt, +} = require('../../lib/prompts/selectHubDBTablePrompt'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.hubdb.subcommands.fetch'; -exports.command = 'fetch [dest]'; +exports.command = 'fetch [table-id] [dest]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { tableId, dest } = options; - - await loadAndValidateOptions(options); + const { derivedAccountId } = options; - const accountId = getAccountId(options); - - trackCommandUsage('hubdb-fetch', null, accountId); + trackCommandUsage('hubdb-fetch', null, derivedAccountId); try { - const { filePath } = await downloadHubDbTable(accountId, tableId, dest); + const promptAnswers = await selectHubDBTablePrompt({ + accountId: derivedAccountId, + options, + skipDestPrompt: false, + }); + const tableId = options.tableId || promptAnswers.tableId; + const dest = options.dest || promptAnswers.dest; + + const { filePath } = await downloadHubDbTable( + derivedAccountId, + tableId, + dest + ); logger.success( i18n(`${i18nKey}.success.fetch`, { @@ -47,7 +55,7 @@ exports.builder = yargs => { addConfigOptions(yargs); addUseEnvironmentOptions(yargs); - yargs.positional('tableId', { + yargs.positional('table-id', { describe: i18n(`${i18nKey}.positionals.tableId.describe`), type: 'string', }); diff --git a/commands/init.ts b/commands/init.ts index dea02efe2..b45ad7bdd 100644 --- a/commands/init.ts +++ b/commands/init.ts @@ -6,8 +6,10 @@ const { createEmptyConfigFile, deleteEmptyConfigFile, updateDefaultAccount, + loadConfig, + configFileExists, } = require('@hubspot/local-dev-lib/config'); -const { addConfigOptions } = require('../lib/commonOpts'); +const { addConfigOptions, addGlobalOptions } = require('../lib/commonOpts'); const { handleExit } = require('../lib/process'); const { checkAndAddConfigToGitignore, @@ -94,19 +96,26 @@ const AUTH_TYPE_NAMES = { [OAUTH_AUTH_METHOD.value]: OAUTH_AUTH_METHOD.name, }; -exports.command = 'init [--account]'; +exports.command = 'init'; exports.describe = i18n(`${i18nKey}.describe`, { configName: DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME, }); exports.handler = async options => { const { - auth: authType = PERSONAL_ACCESS_KEY_AUTH_METHOD.value, - c, - account: optionalAccount, + auth: authTypeFlagValue, + c: configFlagValue, + providedAccountId, disableTracking, + useHiddenConfig, } = options; - const configPath = (c && path.join(getCwd(), c)) || getConfigPath(); + const authType = + (authTypeFlagValue && authTypeFlagValue.toLowerCase()) || + PERSONAL_ACCESS_KEY_AUTH_METHOD.value; + + const configPath = + (configFlagValue && path.join(getCwd(), configFlagValue)) || + getConfigPath('', useHiddenConfig); setLogLevel(options); if (!disableTracking) { @@ -131,16 +140,25 @@ exports.handler = async options => { await trackAuthAction('init', authType, TRACKING_STATUS.STARTED); } - createEmptyConfigFile({ path: configPath }); + const doesOtherConfigFileExist = configFileExists(!useHiddenConfig); + if (doesOtherConfigFileExist) { + const path = getConfigPath('', !useHiddenConfig); + logger.error(i18n(`${i18nKey}.errors.bothConfigFilesNotAllowed`, { path })); + process.exit(EXIT_CODES.ERROR); + } + + trackAuthAction('init', authType, TRACKING_STATUS.STARTED); + createEmptyConfigFile({ path: configPath }, useHiddenConfig); + //Needed to load deprecated config + loadConfig(configPath, options); handleExit(deleteEmptyConfigFile); try { const { accountId, name } = await CONFIG_CREATION_FLOWS[authType]( env, - optionalAccount + providedAccountId ); - const configPath = getConfigPath(); try { checkAndAddConfigToGitignore(configPath); @@ -148,10 +166,15 @@ exports.handler = async options => { debugError(e); } + let newConfigPath = configPath; + if (!newConfigPath && !useHiddenConfig) { + newConfigPath = `${getCwd()}/${DEFAULT_HUBSPOT_CONFIG_YAML_FILE_NAME}`; + } + logger.log(''); logger.success( i18n(`${i18nKey}.success.configFileCreated`, { - configPath, + configPath: newConfigPath, }) ); logger.success( @@ -181,32 +204,43 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.options({ - auth: { - describe: i18n(`${i18nKey}.options.auth.describe`), - type: 'string', - choices: [ - `${PERSONAL_ACCESS_KEY_AUTH_METHOD.value}`, - `${OAUTH_AUTH_METHOD.value}`, - ], - default: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, - defaultDescription: i18n(`${i18nKey}.options.auth.defaultDescription`, { - defaultType: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, - }), - }, - account: { - describe: i18n(`${i18nKey}.options.account.describe`), - type: 'string', - }, - 'disable-tracking': { - type: 'boolean', - hidden: true, - default: false, - }, - }); + yargs + .options({ + 'auth-type': { + describe: i18n(`${i18nKey}.options.authType.describe`), + type: 'string', + choices: [ + `${PERSONAL_ACCESS_KEY_AUTH_METHOD.value}`, + `${OAUTH_AUTH_METHOD.value}`, + ], + default: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, + defaultDescription: i18n( + `${i18nKey}.options.authType.defaultDescription`, + { + defaultType: PERSONAL_ACCESS_KEY_AUTH_METHOD.value, + } + ), + }, + account: { + describe: i18n(`${i18nKey}.options.account.describe`), + type: 'string', + alias: 'a', + }, + 'disable-tracking': { + type: 'boolean', + hidden: true, + default: false, + }, + 'use-hidden-config': { + describe: i18n(`${i18nKey}.options.useHiddenConfig.describe`), + type: 'boolean', + }, + }) + .conflicts('use-hidden-config', 'config'); addConfigOptions(yargs); addTestingOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/lint.ts b/commands/lint.ts index bf75ce09d..bddc727bc 100644 --- a/commands/lint.ts +++ b/commands/lint.ts @@ -10,11 +10,10 @@ const { logError } = require('../lib/errorHandlers/index'); const { addConfigOptions, addAccountOptions, - getAccountId, + addGlobalOptions, } = require('../lib/commonOpts'); const { resolveLocalPath } = require('../lib/filesystem'); const { trackCommandUsage } = require('../lib/usageTracking'); -const { loadAndValidateOptions } = require('../lib/validation'); const { i18n } = require('../lib/lang'); const i18nKey = 'commands.lint'; @@ -64,25 +63,23 @@ function printHublValidationResult({ file, validation }: LintResult): number { export const handler = async options => { const { path: lintPath } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { derivedAccountId } = options; const localPath = resolveLocalPath(lintPath); const groupName = i18n(`${i18nKey}.groupName`, { path: localPath, }); - trackCommandUsage('lint', null, accountId); + trackCommandUsage('lint', null, derivedAccountId); logger.group(groupName); let count = 0; try { - await lint(accountId, localPath, result => { + await lint(derivedAccountId, localPath, result => { count += printHublValidationResult(result); }); } catch (err) { logger.groupEnd(groupName); - logError(err, { accountId }); + logError(err, { accountId: derivedAccountId }); process.exit(EXIT_CODES.ERROR); } logger.groupEnd(groupName); @@ -96,6 +93,7 @@ export const handler = async options => { export const builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); + addGlobalOptions(yargs); yargs.positional('path', { describe: i18n(`${i18nKey}.positionals.path.describe`), type: 'string', diff --git a/commands/list.ts b/commands/list.ts index bfbceb643..9d3ad7f1e 100644 --- a/commands/list.ts +++ b/commands/list.ts @@ -3,7 +3,7 @@ const chalk = require('chalk'); const { addAccountOptions, addConfigOptions, - getAccountId, + addGlobalOptions, addUseEnvironmentOptions, } = require('../lib/commonOpts'); const { trackCommandUsage } = require('../lib/usageTracking'); @@ -15,7 +15,6 @@ const { getDirectoryContentsByPath, } = require('@hubspot/local-dev-lib/api/fileMapper'); const { HUBSPOT_FOLDER, MARKETPLACE_FOLDER } = require('../lib/constants'); -const { loadAndValidateOptions } = require('../lib/validation'); const { i18n } = require('../lib/lang'); const i18nKey = 'commands.list'; @@ -25,14 +24,11 @@ exports.command = 'list [path]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options); - - const { path } = options; + const { path, derivedAccountId } = options; const directoryPath = path || '/'; - const accountId = getAccountId(options); let contentsResp; - trackCommandUsage('list', null, accountId); + trackCommandUsage('list', null, derivedAccountId); logger.debug( i18n(`${i18nKey}.gettingPathContents`, { @@ -41,7 +37,10 @@ exports.handler = async options => { ); try { - const { data } = await getDirectoryContentsByPath(accountId, directoryPath); + const { data } = await getDirectoryContentsByPath( + derivedAccountId, + directoryPath + ); contentsResp = data; } catch (e) { logError(e); @@ -89,6 +88,7 @@ exports.builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/logs.ts b/commands/logs.ts index e5e0dd47c..3570a1416 100644 --- a/commands/logs.ts +++ b/commands/logs.ts @@ -2,8 +2,8 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, + addGlobalOptions, } = require('../lib/commonOpts'); const { trackCommandUsage } = require('../lib/usageTracking'); const { logger } = require('@hubspot/local-dev-lib/logger'); @@ -13,8 +13,8 @@ const { getLatestFunctionLog, } = require('@hubspot/local-dev-lib/api/functions'); const { tailLogs } = require('../lib/serverlessLogs'); -const { loadAndValidateOptions } = require('../lib/validation'); const { i18n } = require('../lib/lang'); +const { promptUser } = require('../lib/prompts/promptUtils'); const { EXIT_CODES } = require('../lib/enums/exitCodes'); const { isHubSpotHttpError } = require('@hubspot/local-dev-lib/errors/index'); @@ -31,8 +31,15 @@ const handleLogsError = (e, accountId, functionPath) => { } }; -const endpointLog = async (accountId, options) => { - const { latest, follow, compact, endpoint: functionPath } = options; +const endpointLog = async (accountId, functionPath, options) => { + const { limit, latest, follow, compact } = options; + const requestOptions = { + limit, + latest, + follow, + compact, + endpoint: functionPath, + }; logger.debug( i18n(`${i18nKey}.gettingLogs`, { @@ -71,7 +78,11 @@ const endpointLog = async (accountId, options) => { } } else { try { - const { data } = await getFunctionLogs(accountId, functionPath, options); + const { data } = await getFunctionLogs( + accountId, + functionPath, + requestOptions + ); logsResp = data; } catch (e) { handleLogsError(e, accountId, functionPath); @@ -80,7 +91,7 @@ const endpointLog = async (accountId, options) => { } if (logsResp) { - return outputLogs(logsResp, options); + return outputLogs(logsResp, requestOptions); } }; @@ -88,15 +99,21 @@ exports.command = 'logs [endpoint]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options); - - const { latest } = options; + const { endpoint: endpointArgValue, latest, derivedAccountId } = options; - const accountId = getAccountId(options); + trackCommandUsage('logs', { latest }, derivedAccountId); - trackCommandUsage('logs', { latest }, accountId); + const { endpointPromptValue } = await promptUser({ + name: 'endpointPromptValue', + message: i18n(`${i18nKey}.endpointPrompt`), + when: !endpointArgValue, + }); - endpointLog(accountId, options); + endpointLog( + derivedAccountId, + endpointArgValue || endpointPromptValue, + options + ); }; exports.builder = yargs => { @@ -116,18 +133,16 @@ exports.builder = yargs => { type: 'boolean', }, follow: { - alias: ['t', 'tail', 'f'], + alias: ['f'], describe: i18n(`${i18nKey}.options.follow.describe`), type: 'boolean', }, limit: { - alias: ['limit', 'n', 'max-count'], describe: i18n(`${i18nKey}.options.limit.describe`), type: 'number', }, }) - .conflicts('follow', 'limit') - .conflicts('functionName', 'endpoint'); + .conflicts('follow', 'limit'); yargs.example([ ['$0 logs my-endpoint', i18n(`${i18nKey}.examples.default`)], @@ -138,6 +153,7 @@ exports.builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/module.ts b/commands/module.ts index ff773450d..ccd979d4c 100644 --- a/commands/module.ts +++ b/commands/module.ts @@ -1,6 +1,10 @@ // @ts-nocheck const marketplaceValidate = require('./module/marketplace-validate'); -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { + addConfigOptions, + addAccountOptions, + addGlobalOptions, +} = require('../lib/commonOpts'); // const { i18n } = require('../lib/lang'); // const i18nKey = 'commands.module'; @@ -11,6 +15,7 @@ exports.describe = false; //i18n(`${i18nKey}.describe`); exports.builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); + addGlobalOptions(yargs); yargs.command(marketplaceValidate).demandCommand(1, ''); diff --git a/commands/module/marketplace-validate.ts b/commands/module/marketplace-validate.ts index b376ab779..1dc9f2bcd 100644 --- a/commands/module/marketplace-validate.ts +++ b/commands/module/marketplace-validate.ts @@ -4,9 +4,7 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { kickOffValidation, @@ -23,13 +21,9 @@ exports.command = 'marketplace-validate '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { src } = options; + const { src, derivedAccountId } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - - trackCommandUsage('validate', null, accountId); + trackCommandUsage('validate', null, derivedAccountId); SpinniesManager.init(); @@ -40,13 +34,17 @@ exports.handler = async options => { }); const assetType = 'MODULE'; - const validationId = await kickOffValidation(accountId, assetType, src); - await pollForValidationFinish(accountId, validationId); + const validationId = await kickOffValidation( + derivedAccountId, + assetType, + src + ); + await pollForValidationFinish(derivedAccountId, validationId); SpinniesManager.remove('marketplaceValidation'); const validationResults = await fetchValidationResults( - accountId, + derivedAccountId, validationId ); processValidationErrors(i18nKey, validationResults); diff --git a/commands/mv.ts b/commands/mv.ts index afad7e309..771446e58 100644 --- a/commands/mv.ts +++ b/commands/mv.ts @@ -7,11 +7,10 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, + addGlobalOptions, } = require('../lib/commonOpts'); const { trackCommandUsage } = require('../lib/usageTracking'); const { isPathFolder } = require('../lib/filesystem'); -const { loadAndValidateOptions } = require('../lib/validation'); const { i18n } = require('../lib/lang'); const { uiBetaTag } = require('../lib/ui'); @@ -30,18 +29,19 @@ exports.command = 'mv '; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); + const { srcPath, destPath, derivedAccountId } = options; - const { srcPath, destPath } = options; - const accountId = getAccountId(options); - - trackCommandUsage('mv', null, accountId); + trackCommandUsage('mv', null, derivedAccountId); try { - await moveFile(accountId, srcPath, getCorrectedDestPath(srcPath, destPath)); + await moveFile( + derivedAccountId, + srcPath, + getCorrectedDestPath(srcPath, destPath) + ); logger.success( i18n(`${i18nKey}.move`, { - accountId, + accountId: derivedAccountId, destPath, srcPath, }) @@ -49,7 +49,7 @@ exports.handler = async options => { } catch (error) { logger.error( i18n(`${i18nKey}.errors.moveFailed`, { - accountId, + accountId: derivedAccountId, destPath, srcPath, }) @@ -65,7 +65,7 @@ exports.handler = async options => { logError( error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, srcPath, destPath, }) @@ -75,9 +75,6 @@ exports.handler = async options => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); yargs.positional('srcPath', { describe: 'Remote hubspot path', type: 'string', @@ -86,5 +83,10 @@ exports.builder = yargs => { describe: 'Remote hubspot path', type: 'string', }); + + addConfigOptions(yargs); + addAccountOptions(yargs); + addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/open.ts b/commands/open.ts index f1f7e16d4..aad234f42 100644 --- a/commands/open.ts +++ b/commands/open.ts @@ -1,9 +1,10 @@ // @ts-nocheck +import { EXIT_CODES } from '../lib/enums/exitCodes'; const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, + addGlobalOptions, } = require('../lib/commonOpts'); const { trackCommandUsage } = require('../lib/usageTracking'); const { logSiteLinks, getSiteLinksAsArray, openLink } = require('../lib/links'); @@ -32,20 +33,19 @@ exports.command = 'open [shortcut]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { shortcut, list } = options; - const accountId = getAccountId(options); + const { shortcut, list, derivedAccountId } = options; - trackCommandUsage('open', null, accountId); + trackCommandUsage('open', null, derivedAccountId); if (shortcut === undefined && !list) { - const choice = await createListPrompt(accountId); - openLink(accountId, choice.open); - } else if (list || shortcut === 'list') { - logSiteLinks(accountId); - return; + const choice = await createListPrompt(derivedAccountId); + openLink(derivedAccountId, choice.open); + } else if (list) { + logSiteLinks(derivedAccountId); } else { - openLink(accountId, shortcut); + openLink(derivedAccountId, shortcut); } + process.exit(EXIT_CODES.SUCCESS); }; exports.builder = yargs => { @@ -71,6 +71,7 @@ exports.builder = yargs => { addConfigOptions(yargs); addAccountOptions(yargs); addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); return yargs; }; diff --git a/commands/project.ts b/commands/project.ts index e81dead87..594ecb64c 100644 --- a/commands/project.ts +++ b/commands/project.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { addGlobalOptions } = require('../lib/commonOpts'); const { i18n } = require('../lib/lang'); const { uiBetaTag } = require('../lib/ui'); const deploy = require('./project/deploy'); @@ -18,12 +18,11 @@ const installDeps = require('./project/installDeps'); const i18nKey = 'commands.project'; -exports.command = 'project'; +exports.command = ['project', 'projects']; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); + addGlobalOptions(yargs); yargs .command(create) diff --git a/commands/project/__tests__/deploy.test.ts b/commands/project/__tests__/deploy.test.ts index 2d5c5d54c..9e53acba4 100644 --- a/commands/project/__tests__/deploy.test.ts +++ b/commands/project/__tests__/deploy.test.ts @@ -15,15 +15,11 @@ const ui = require('../../../lib/ui'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../../lib/validation'); -const { - getProjectConfig, - pollDeployStatus, - getProjectDetailUrl, -} = require('../../../lib/projects'); +const { getProjectConfig } = require('../../../lib/projects'); +const { getProjectDetailUrl } = require('../../../lib/projects/urls'); +const { pollDeployStatus } = require('../../../lib/projects/buildAndDeploy'); const { projectNamePrompt } = require('../../../lib/prompts/projectNamePrompt'); const { promptUser } = require('../../../lib/prompts/promptUtils'); const { trackCommandUsage } = require('../../../lib/usageTracking'); @@ -36,6 +32,8 @@ jest.mock('@hubspot/local-dev-lib/config'); jest.mock('../../../lib/commonOpts'); jest.mock('../../../lib/validation'); jest.mock('../../../lib/projects'); +jest.mock('../../../lib/projects/urls'); +jest.mock('../../../lib/projects/buildAndDeploy'); jest.mock('../../../lib/prompts/projectNamePrompt'); jest.mock('../../../lib/prompts/promptUtils'); jest.mock('../../../lib/usageTracking'); @@ -50,7 +48,7 @@ const deployCommand = require('../deploy'); describe('commands/project/deploy', () => { const projectFlag = 'project'; const buildFlag = 'build'; - const buildAliases = ['buildId']; + const buildAliases = ['build-id']; describe('command', () => { it('should have the correct command structure', () => { @@ -101,7 +99,6 @@ describe('commands/project/deploy', () => { describe('handler', () => { let projectConfig; let processExitSpy; - const accountId = 1234567890; const accountType = 'STANDARD'; let options; const projectDetails = { @@ -118,7 +115,7 @@ describe('commands/project/deploy', () => { options = { project: 'project name from options', buildId: 2, - accountId, + derivedAccountId: 1234567890, }; projectConfig = { name: 'project name from config', @@ -126,7 +123,9 @@ describe('commands/project/deploy', () => { getProjectConfig.mockResolvedValue({ projectConfig }); projectNamePrompt.mockResolvedValue({ projectName: 'fooo' }); getProjectDetailUrl.mockReturnValue(projectDetailUrl); - getAccountId.mockReturnValue(accountId); + uiLinkSpy.mockImplementation(text => { + return text; + }); getAccountConfig.mockReturnValue({ accountType }); fetchProject.mockResolvedValue({ data: projectDetails }); deployProject.mockResolvedValue({ data: deployDetails }); @@ -135,22 +134,10 @@ describe('commands/project/deploy', () => { processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {}); }); - it('should load and validate the options', async () => { - await deployCommand.handler(options); - expect(loadAndValidateOptions).toHaveBeenCalledTimes(1); - expect(loadAndValidateOptions).toHaveBeenCalledWith(options); - }); - - it('should get the account id from the options', async () => { - await deployCommand.handler(options); - expect(getAccountId).toHaveBeenCalledTimes(1); - expect(getAccountId).toHaveBeenCalledWith(options); - }); - it('should load the account config for the correct account id', async () => { await deployCommand.handler(options); expect(getAccountConfig).toHaveBeenCalledTimes(1); - expect(getAccountConfig).toHaveBeenCalledWith(accountId); + expect(getAccountConfig).toHaveBeenCalledWith(options.derivedAccountId); }); it('should track the command usage', async () => { @@ -159,7 +146,7 @@ describe('commands/project/deploy', () => { expect(trackCommandUsage).toHaveBeenCalledWith( 'project-deploy', { type: accountType }, - accountId + options.derivedAccountId ); }); @@ -178,7 +165,7 @@ describe('commands/project/deploy', () => { it('should prompt for the project name', async () => { await deployCommand.handler(options); expect(projectNamePrompt).toHaveBeenCalledTimes(1); - expect(projectNamePrompt).toHaveBeenCalledWith(accountId, { + expect(projectNamePrompt).toHaveBeenCalledWith(options.derivedAccountId, { project: options.project, }); }); @@ -187,7 +174,7 @@ describe('commands/project/deploy', () => { delete options.project; await deployCommand.handler(options); expect(projectNamePrompt).toHaveBeenCalledTimes(1); - expect(projectNamePrompt).toHaveBeenCalledWith(accountId, { + expect(projectNamePrompt).toHaveBeenCalledWith(options.derivedAccountId, { project: projectConfig.name, }); }); @@ -195,7 +182,10 @@ describe('commands/project/deploy', () => { it('should fetch the project details', async () => { await deployCommand.handler(options); expect(fetchProject).toHaveBeenCalledTimes(1); - expect(fetchProject).toHaveBeenCalledWith(accountId, options.project); + expect(fetchProject).toHaveBeenCalledWith( + options.derivedAccountId, + options.project + ); }); it('should use the name from the prompt if no others are defined', async () => { @@ -207,9 +197,15 @@ describe('commands/project/deploy', () => { await deployCommand.handler(options); expect(projectNamePrompt).toHaveBeenCalledTimes(1); - expect(projectNamePrompt).toHaveBeenCalledWith(accountId, {}); + expect(projectNamePrompt).toHaveBeenCalledWith( + options.derivedAccountId, + {} + ); expect(fetchProject).toHaveBeenCalledTimes(1); - expect(fetchProject).toHaveBeenCalledWith(accountId, promptProjectName); + expect(fetchProject).toHaveBeenCalledWith( + options.derivedAccountId, + promptProjectName + ); }); it('should log an error and exit when latest build is not defined', async () => { @@ -282,7 +278,7 @@ describe('commands/project/deploy', () => { await deployCommand.handler(options); expect(deployProject).toHaveBeenCalledTimes(1); expect(deployProject).toHaveBeenCalledWith( - accountId, + options.derivedAccountId, options.project, options.buildId ); @@ -309,7 +305,7 @@ describe('commands/project/deploy', () => { await deployCommand.handler(options); expect(pollDeployStatus).toHaveBeenCalledTimes(1); expect(pollDeployStatus).toHaveBeenCalledWith( - accountId, + options.derivedAccountId, options.project, deployDetails.id, options.buildId @@ -382,7 +378,7 @@ describe('commands/project/deploy', () => { expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( - `The request for 'project deploy' in account ${accountId} failed due to a client error.` + `The request for 'project deploy' in account ${options.derivedAccountId} failed due to a client error.` ); expect(processExitSpy).toHaveBeenCalledTimes(1); expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR); diff --git a/commands/project/__tests__/logs.test.ts b/commands/project/__tests__/logs.test.ts index be81cfe41..7faa46639 100644 --- a/commands/project/__tests__/logs.test.ts +++ b/commands/project/__tests__/logs.test.ts @@ -1,10 +1,9 @@ // @ts-nocheck const yargs = require('yargs'); +const { addUseEnvironmentOptions } = require('../../../lib/commonOpts'); const { - addUseEnvironmentOptions, - getAccountId, -} = require('../../../lib/commonOpts'); -const ProjectLogsManager = require('../../../lib/ProjectLogsManager'); + ProjectLogsManager, +} = require('../../../lib/projects/ProjectLogsManager'); const { projectLogsPrompt, } = require('../../../lib/prompts/projectsLogsPrompt'); @@ -19,7 +18,7 @@ jest.mock('@hubspot/local-dev-lib/logger'); jest.mock('../../../lib/commonOpts'); jest.mock('../../../lib/usageTracking'); jest.mock('../../../lib/validation'); -jest.mock('../../../lib/ProjectLogsManager'); +jest.mock('../../../lib/projects/ProjectLogsManager'); jest.mock('../../../lib/prompts/projectsLogsPrompt'); jest.mock('../../../lib/ui/table'); jest.mock('../../../lib/errorHandlers'); @@ -97,42 +96,34 @@ describe('commands/project/logs', () => { }); describe('handler', () => { - const accountId = 12345678; - beforeEach(() => { - getAccountId.mockReturnValue(accountId); projectLogsPrompt.mockResolvedValue({ functionName: 'foo' }); }); - it('should get the account id', async () => { - const options = { - foo: 'bar', - }; - await logsCommand.handler(options); - expect(getAccountId).toHaveBeenCalledTimes(1); - expect(getAccountId).toHaveBeenCalledWith(options); - }); - it('should track the command usage', async () => { const options = { foo: 'bar', + derivedAccountId: 12345678, }; await logsCommand.handler(options); expect(trackCommandUsage).toHaveBeenCalledTimes(1); expect(trackCommandUsage).toHaveBeenCalledWith( 'project-logs', null, - accountId + options.derivedAccountId ); }); it('should initialize the ProjectLogsManager', async () => { const options = { foo: 'bar', + derivedAccountId: 12345678, }; await logsCommand.handler(options); expect(ProjectLogsManager.init).toHaveBeenCalledTimes(1); - expect(ProjectLogsManager.init).toHaveBeenCalledWith(accountId); + expect(ProjectLogsManager.init).toHaveBeenCalledWith( + options.derivedAccountId + ); }); it('should prompt the user for input', async () => { @@ -167,6 +158,9 @@ describe('commands/project/logs', () => { }); it('should log public functions correctly', async () => { + const options = { + derivedAccountId: 12345678, + }; const functionNames = ['function1', 'function2']; const selectedFunction = 'function1'; ProjectLogsManager.getFunctionNames.mockReturnValue(functionNames); @@ -178,12 +172,13 @@ describe('commands/project/logs', () => { getTableHeader.mockReturnValue(tableHeaders); ProjectLogsManager.isPublicFunction = true; - ProjectLogsManager.accountId = accountId; + ProjectLogsManager.accountId = options.derivedAccountId; ProjectLogsManager.functionName = selectedFunction; ProjectLogsManager.endpointName = 'my-endpoint'; ProjectLogsManager.appId = 123456; - await logsCommand.handler({}); + await logsCommand.handler(options); + expect(getTableHeader).toHaveBeenCalledTimes(1); expect(getTableHeader).toHaveBeenCalledWith([ 'Account', @@ -206,7 +201,7 @@ describe('commands/project/logs', () => { expect(uiLinkSpy).toHaveBeenCalledTimes(1); expect(uiLinkSpy).toHaveBeenCalledWith( 'View function logs in HubSpot', - `https://app.hubspot.com/private-apps/${accountId}/${ProjectLogsManager.appId}/logs/serverlessGatewayExecution?path=${ProjectLogsManager.endpointName}` + `https://app.hubspot.com/private-apps/${options.derivedAccountId}/${ProjectLogsManager.appId}/logs/serverlessGatewayExecution?path=${ProjectLogsManager.endpointName}` ); expect(uiLineSpy).toHaveBeenCalledTimes(1); }); @@ -214,6 +209,9 @@ describe('commands/project/logs', () => { it('should log private functions correctly', async () => { const functionNames = ['function1', 'function2']; const selectedFunction = 'function1'; + const options = { + derivedAccountId: 12345678, + }; ProjectLogsManager.getFunctionNames.mockReturnValue(functionNames); projectLogsPrompt.mockReturnValue({ @@ -224,11 +222,12 @@ describe('commands/project/logs', () => { getTableHeader.mockReturnValue(tableHeaders); ProjectLogsManager.isPublicFunction = false; - ProjectLogsManager.accountId = accountId; + ProjectLogsManager.accountId = options.derivedAccountId; ProjectLogsManager.functionName = selectedFunction; ProjectLogsManager.appId = 456789; - await logsCommand.handler({}); + await logsCommand.handler(options); + expect(getTableHeader).toHaveBeenCalledTimes(1); expect(getTableHeader).toHaveBeenCalledWith(['Account', 'Function']); @@ -243,25 +242,27 @@ describe('commands/project/logs', () => { expect(uiLinkSpy).toHaveBeenCalledWith( 'View function logs in HubSpot', - `https://app.hubspot.com/private-apps/${accountId}/${ProjectLogsManager.appId}/logs/crm?serverlessFunction=${selectedFunction}` + `https://app.hubspot.com/private-apps/${options.derivedAccountId}/${ProjectLogsManager.appId}/logs/crm?serverlessFunction=${selectedFunction}` ); expect(uiLineSpy).toHaveBeenCalledTimes(1); }); it('should handle errors correctly', async () => { + const options = { + derivedAccountId: 12345678, + }; const error = new Error('Something went wrong'); ProjectLogsManager.init.mockImplementation(() => { throw error; }); ProjectLogsManager.projectName = 'Super cool project'; - - await logsCommand.handler({}); + await logsCommand.handler(options); expect(logError).toHaveBeenCalledTimes(1); expect(logError).toHaveBeenCalledWith(error, { - accountId: accountId, + accountId: options.derivedAccountId, projectName: ProjectLogsManager.projectName, }); diff --git a/commands/project/add.ts b/commands/project/add.ts index 4c522883a..d8767582f 100644 --- a/commands/project/add.ts +++ b/commands/project/add.ts @@ -1,6 +1,5 @@ // @ts-nocheck const { logger } = require('@hubspot/local-dev-lib/logger'); -const { getAccountId } = require('@hubspot/local-dev-lib/config'); const { logError } = require('../../lib/errorHandlers/index'); const { fetchReleaseData } = require('@hubspot/local-dev-lib/github'); @@ -11,7 +10,6 @@ const { createProjectComponent, getProjectComponentsByVersion, } = require('../../lib/projects'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { uiBetaTag } = require('../../lib/ui'); const { HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, @@ -23,14 +21,12 @@ exports.command = 'add'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); + const { derivedAccountId } = options; logger.log(''); logger.log(i18n(`${i18nKey}.creatingComponent.message`)); logger.log(''); - const accountId = getAccountId(options); - const releaseData = await fetchReleaseData( HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH ); @@ -48,7 +44,7 @@ exports.handler = async options => { component = components.find(t => t.path === options.type); } - trackCommandUsage('project-add', null, accountId); + trackCommandUsage('project-add', null, derivedAccountId); try { await createProjectComponent(component, name, projectComponentsVersion); diff --git a/commands/project/cloneApp.ts b/commands/project/cloneApp.ts index e26340354..5a8ec9535 100644 --- a/commands/project/cloneApp.ts +++ b/commands/project/cloneApp.ts @@ -4,14 +4,12 @@ const fs = require('fs'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage, trackCommandMetadataUsage, } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); const { selectPublicAppPrompt, @@ -48,13 +46,11 @@ exports.command = 'clone-app'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); + const { derivedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); + const accountName = uiAccountDescription(derivedAccountId); - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); - const accountName = uiAccountDescription(accountId); - - trackCommandUsage('clone-app', {}, accountId); + trackCommandUsage('clone-app', {}, derivedAccountId); if (!isAppDeveloperAccount(accountConfig)) { uiLine(); @@ -70,27 +66,34 @@ exports.handler = async options => { } let appId; - let name; - let location; + let projectName; + let projectDest; try { appId = options.appId; if (!appId) { const appIdResponse = await selectPublicAppPrompt({ - accountId, + accountId: derivedAccountId, accountName, options, isMigratingApp: false, }); appId = appIdResponse.appId; } + const { name, dest } = await createProjectPrompt('', options, true); - const projectResponse = await createProjectPrompt('', options, true); - name = projectResponse.name; - location = projectResponse.location; + projectName = name; + projectDest = options.dest || dest; } catch (error) { - logError(error, new ApiErrorContext({ accountId })); + logError(error, new ApiErrorContext({ accountId: derivedAccountId })); process.exit(EXIT_CODES.ERROR); } + + await trackCommandMetadataUsage( + 'clone-app', + { status: 'STARTED' }, + derivedAccountId + ); + try { SpinniesManager.init(); @@ -100,22 +103,22 @@ exports.handler = async options => { const { data: { exportId }, - } = await cloneApp(accountId, appId); - const { status } = await poll(checkCloneStatus, accountId, exportId); + } = await cloneApp(derivedAccountId, appId); + const { status } = await poll(checkCloneStatus, derivedAccountId, exportId); if (status === 'SUCCESS') { // Ensure correct project folder structure exists - const baseDestPath = path.resolve(getCwd(), location); + const baseDestPath = path.resolve(getCwd(), projectDest); const absoluteDestPath = path.resolve(baseDestPath, 'src', 'app'); fs.mkdirSync(absoluteDestPath, { recursive: true }); // Extract zipped app files and place them in correct directory const { data: zippedApp } = await downloadClonedProject( - accountId, + derivedAccountId, exportId ); await extractZipArchive( zippedApp, - sanitizeFileName(name), + sanitizeFileName(projectName), absoluteDestPath, { includesRootDir: true, @@ -126,22 +129,12 @@ exports.handler = async options => { // Create hsproject.json file const configPath = path.join(baseDestPath, PROJECT_CONFIG_FILE); const configContent = { - name, + name: projectName, srcDir: 'src', platformVersion: '2023.2', }; const success = writeProjectConfig(configPath, configContent); - trackCommandMetadataUsage( - 'clone-app', - { - type: name, - assetType: appId, - successful: success, - }, - accountId - ); - SpinniesManager.succeed('cloneApp', { text: i18n(`${i18nKey}.cloneStatus.done`), succeedColor: 'white', @@ -154,15 +147,15 @@ exports.handler = async options => { } logger.log(''); uiLine(); - logger.success(i18n(`${i18nKey}.cloneStatus.success`, { location })); + logger.success(i18n(`${i18nKey}.cloneStatus.success`, { dest })); logger.log(''); process.exit(EXIT_CODES.SUCCESS); } } catch (error) { - trackCommandMetadataUsage( + await trackCommandMetadataUsage( 'clone-app', - { projectName: name, appId, status: 'FAILURE', error }, - accountId + { status: 'FAILURE' }, + derivedAccountId ); SpinniesManager.fail('cloneApp', { @@ -172,21 +165,28 @@ exports.handler = async options => { // Migrations endpoints return a response object with an errors property. The errors property contains an array of errors. if (error.errors && Array.isArray(error.errors)) { error.errors.forEach(e => - logError(e, new ApiErrorContext({ accountId })) + logError(e, new ApiErrorContext({ accountId: derivedAccountId })) ); } else { - logError(error, new ApiErrorContext({ accountId })); + logError(error, new ApiErrorContext({ accountId: derivedAccountId })); } } + + await trackCommandMetadataUsage( + 'clone-app', + { status: 'SUCCESS' }, + derivedAccountId + ); + process.exit(EXIT_CODES.SUCCESS); }; exports.builder = yargs => { yargs.options({ - location: { - describe: i18n(`${i18nKey}.options.location.describe`), + dest: { + describe: i18n(`${i18nKey}.options.dest.describe`), type: 'string', }, - appId: { + 'app-id': { describe: i18n(`${i18nKey}.options.appId.describe`), type: 'number', }, diff --git a/commands/project/create.ts b/commands/project/create.ts index dc989aeda..4d4b25496 100644 --- a/commands/project/create.ts +++ b/commands/project/create.ts @@ -2,11 +2,9 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { getCwd } = require('@hubspot/local-dev-lib/path'); const path = require('path'); const chalk = require('chalk'); @@ -28,9 +26,7 @@ exports.command = 'create'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { derivedAccountId } = options; const hasCustomTemplateSource = Boolean(options.templateSource); @@ -43,15 +39,19 @@ exports.handler = async options => { githubRef = releaseData.tag_name; } - const { name, template, location } = await createProjectPrompt( + const { name, template, dest } = await createProjectPrompt( githubRef, options ); - trackCommandUsage('project-create', { type: template.name }, accountId); + trackCommandUsage( + 'project-create', + { type: template.name }, + derivedAccountId + ); await createProjectConfig( - path.resolve(getCwd(), options.location || location), + path.resolve(getCwd(), options.dest || dest), options.name || name, template, options.templateSource, @@ -74,15 +74,15 @@ exports.builder = yargs => { describe: i18n(`${i18nKey}.options.name.describe`), type: 'string', }, - location: { - describe: i18n(`${i18nKey}.options.location.describe`), + dest: { + describe: i18n(`${i18nKey}.options.dest.describe`), type: 'string', }, template: { describe: i18n(`${i18nKey}.options.template.describe`), type: 'string', }, - templateSource: { + 'template-source': { describe: i18n(`${i18nKey}.options.templateSource.describe`), type: 'string', }, diff --git a/commands/project/deploy.ts b/commands/project/deploy.ts index d9d184088..5d9245d12 100644 --- a/commands/project/deploy.ts +++ b/commands/project/deploy.ts @@ -3,7 +3,6 @@ const chalk = require('chalk'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); @@ -13,12 +12,9 @@ const { deployProject, fetchProject, } = require('@hubspot/local-dev-lib/api/projects'); -const { loadAndValidateOptions } = require('../../lib/validation'); -const { - getProjectConfig, - pollDeployStatus, - getProjectDetailUrl, -} = require('../../lib/projects'); +const { getProjectConfig } = require('../../lib/projects'); +const { pollDeployStatus } = require('../../lib/projects/buildAndDeploy'); +const { getProjectDetailUrl } = require('../../lib/projects/urls'); const { projectNamePrompt } = require('../../lib/prompts/projectNamePrompt'); const { promptUser } = require('../../lib/prompts/promptUtils'); const { i18n } = require('../../lib/lang'); @@ -63,14 +59,12 @@ const validateBuildId = ( }; exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); + const { derivedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); const { project: projectOption, buildId: buildIdOption } = options; const accountType = accountConfig && accountConfig.accountType; - trackCommandUsage('project-deploy', { type: accountType }, accountId); + trackCommandUsage('project-deploy', { type: accountType }, derivedAccountId); const { projectConfig } = await getProjectConfig(); @@ -80,7 +74,7 @@ exports.handler = async options => { projectName = projectConfig.name; } - const namePromptResponse = await projectNamePrompt(accountId, { + const namePromptResponse = await projectNamePrompt(derivedAccountId, { project: projectName, }); @@ -93,7 +87,7 @@ exports.handler = async options => { try { const { data: { latestBuild, deployedBuildId }, - } = await fetchProject(accountId, projectName); + } = await fetchProject(derivedAccountId, projectName); if (!latestBuild || !latestBuild.buildId) { logger.error(i18n(`${i18nKey}.errors.noBuilds`)); @@ -106,7 +100,7 @@ exports.handler = async options => { deployedBuildId, latestBuild.buildId, projectName, - accountId + derivedAccountId ); if (validationResult !== true) { logger.error(validationResult); @@ -120,16 +114,15 @@ exports.handler = async options => { latestBuild.buildId === deployedBuildId ? undefined : latestBuild.buildId, - validate: () => + validate: buildId => validateBuildId( buildId, deployedBuildId, latestBuild.buildId, projectName, - accountId + derivedAccountId ), }); - buildIdToDeploy = deployBuildIdPromptResponse.buildId; } @@ -139,7 +132,7 @@ exports.handler = async options => { } const { data: deployResp } = await deployProject( - accountId, + derivedAccountId, projectName, buildIdToDeploy ); @@ -154,7 +147,7 @@ exports.handler = async options => { } await pollDeployStatus( - accountId, + derivedAccountId, projectName, deployResp.id, buildIdToDeploy @@ -164,7 +157,7 @@ exports.handler = async options => { logger.error( i18n(`${i18nKey}.errors.projectNotFound`, { projectName: chalk.bold(projectName), - accountIdentifier: uiAccountDescription(accountId), + accountIdentifier: uiAccountDescription(derivedAccountId), command: uiCommandReference('hs project upload'), }) ); @@ -173,7 +166,10 @@ exports.handler = async options => { } else { logError( e, - new ApiErrorContext({ accountId, request: 'project deploy' }) + new ApiErrorContext({ + accountId: derivedAccountId, + request: 'project deploy', + }) ); } return process.exit(EXIT_CODES.ERROR); @@ -187,7 +183,7 @@ exports.builder = yargs => { type: 'string', }, build: { - alias: ['buildId'], + alias: ['build-id'], describe: i18n(`${i18nKey}.options.build.describe`), type: 'number', }, diff --git a/commands/project/dev.ts b/commands/project/dev.ts index fe559e443..762028634 100644 --- a/commands/project/dev.ts +++ b/commands/project/dev.ts @@ -2,12 +2,10 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, addTestingOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { handleExit } = require('../../lib/process'); const { i18n } = require('../../lib/lang'); const { logger } = require('@hubspot/local-dev-lib/logger'); @@ -37,7 +35,7 @@ const { findProjectComponents, getProjectComponentTypes, COMPONENT_TYPES, -} = require('../../lib/projectStructure'); +} = require('../../lib/projects/structure'); const { confirmDefaultAccountIsTarget, suggestRecommendedNestedAccount, @@ -53,16 +51,15 @@ const { const i18nKey = 'commands.project.subcommands.dev'; -exports.command = 'dev [--account]'; +exports.command = 'dev'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); - const env = getValidEnv(getEnv(accountId)); + const { derivedAccountId, providedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); + const env = getValidEnv(getEnv(derivedAccountId)); - trackCommandUsage('project-dev', null, accountId); + trackCommandUsage('project-dev', null, derivedAccountId); const { projectConfig, projectDir } = await getProjectConfig(); @@ -107,13 +104,14 @@ exports.handler = async options => { isDeveloperTestAccount(accountConfig) || (!hasPublicApps && isSandbox(accountConfig)); - // The account that the project must exist in - let targetProjectAccountId = options.account ? accountId : null; + // targetProjectAccountId and targetTestingAccountId are set to null if --account flag is not provided. + // By setting them to null, we can later check if they need to be assigned based on the default account configuration and the type of app. + let targetProjectAccountId = providedAccountId ? derivedAccountId : null; // The account that we are locally testing against - let targetTestingAccountId = options.account ? accountId : null; + let targetTestingAccountId = providedAccountId ? derivedAccountId : null; // Check that the default account or flag option is valid for the type of app in this project - if (options.account) { + if (providedAccountId) { checkIfAccountFlagIsSupported(accountConfig, hasPublicApps); if (hasPublicApps) { @@ -125,7 +123,7 @@ exports.handler = async options => { // The user is targeting an account type that we recommend developing on if (!targetProjectAccountId && defaultAccountIsRecommendedType) { - targetTestingAccountId = accountId; + targetTestingAccountId = derivedAccountId; await confirmDefaultAccountIsTarget(accountConfig, hasPublicApps); @@ -133,7 +131,7 @@ exports.handler = async options => { checkIfParentAccountIsAuthed(accountConfig); targetProjectAccountId = accountConfig.parentAccountId; } else { - targetProjectAccountId = accountId; + targetProjectAccountId = derivedAccountId; } } @@ -167,7 +165,7 @@ exports.handler = async options => { if (createNewSandbox) { targetProjectAccountId = await createSandboxForLocalDev( - accountId, + derivedAccountId, accountConfig, env ); @@ -176,11 +174,11 @@ exports.handler = async options => { } if (createNewDeveloperTestAccount) { targetTestingAccountId = await createDeveloperTestAccountForLocalDev( - accountId, + derivedAccountId, accountConfig, env ); - targetProjectAccountId = accountId; + targetProjectAccountId = derivedAccountId; } // eslint-disable-next-line prefer-const diff --git a/commands/project/download.ts b/commands/project/download.ts index 9f96b5b94..e770d20fe 100644 --- a/commands/project/download.ts +++ b/commands/project/download.ts @@ -3,7 +3,8 @@ const path = require('path'); const chalk = require('chalk'); const { - getAccountId, + addAccountOptions, + addConfigOptions, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); @@ -16,7 +17,6 @@ const { fetchProjectBuilds, } = require('@hubspot/local-dev-lib/api/projects'); const { ensureProjectExists, getProjectConfig } = require('../../lib/projects'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { downloadProjectPrompt, } = require('../../lib/prompts/downloadProjectPrompt'); @@ -26,12 +26,10 @@ const { uiBetaTag } = require('../../lib/ui'); const i18nKey = 'commands.project.subcommands.download'; const { EXIT_CODES } = require('../../lib/enums/exitCodes'); -exports.command = 'download [--project]'; +exports.command = 'download'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - const { projectConfig } = await getProjectConfig(); if (projectConfig) { @@ -39,17 +37,15 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } - const { project, dest, buildNumber } = options; + const { project, dest, build, derivedAccountId } = options; const { project: promptedProjectName } = await downloadProjectPrompt(options); let projectName = promptedProjectName || project; - const accountId = getAccountId(options); - - trackCommandUsage('project-download', null, accountId); + trackCommandUsage('project-download', null, derivedAccountId); try { const { projectExists } = await ensureProjectExists( - accountId, + derivedAccountId, projectName, { allowCreate: false, @@ -61,22 +57,21 @@ exports.handler = async options => { logger.error( i18n(`${i18nKey}.errors.projectNotFound`, { projectName: chalk.bold(projectName), - accountId: chalk.bold(accountId), + accountId: chalk.bold(derivedAccountId), }) ); - const { name: promptedProjectName } = await downloadProjectPrompt( - options - ); + const { name: promptedProjectName } = + await downloadProjectPrompt(options); projectName = promptedProjectName || project; } const absoluteDestPath = dest ? path.resolve(getCwd(), dest) : getCwd(); - let buildNumberToDownload = buildNumber; + let buildNumberToDownload = build; if (!buildNumberToDownload) { const { data: projectBuildsResult } = await fetchProjectBuilds( - accountId, + derivedAccountId, projectName ); @@ -89,7 +84,7 @@ exports.handler = async options => { } const { data: zippedProject } = await downloadProject( - accountId, + derivedAccountId, projectName, buildNumberToDownload ); @@ -111,13 +106,18 @@ exports.handler = async options => { } catch (e) { logError( e, - new ApiErrorContext({ accountId, request: 'project download' }) + new ApiErrorContext({ + accountId: derivedAccountId, + request: 'project download', + }) ); process.exit(EXIT_CODES.ERROR); } }; exports.builder = yargs => { + addAccountOptions(yargs); + addConfigOptions(yargs); addUseEnvironmentOptions(yargs); yargs.options({ @@ -129,8 +129,9 @@ exports.builder = yargs => { describe: i18n(`${i18nKey}.options.dest.describe`), type: 'string', }, - buildNumber: { - describe: i18n(`${i18nKey}.options.buildNumber.describe`), + build: { + describe: i18n(`${i18nKey}.options.build.describe`), + alias: ['build-id'], type: 'number', }, }); diff --git a/commands/project/listBuilds.ts b/commands/project/listBuilds.ts index 91eff2697..edb41ff35 100644 --- a/commands/project/listBuilds.ts +++ b/commands/project/listBuilds.ts @@ -1,10 +1,7 @@ // @ts-nocheck -const path = require('path'); - const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); @@ -16,59 +13,61 @@ const { fetchProjectBuilds, } = require('@hubspot/local-dev-lib/api/projects'); const { getTableContents, getTableHeader } = require('../../lib/ui/table'); -const { getCwd } = require('@hubspot/local-dev-lib/path'); const { uiBetaTag, uiLink } = require('../../lib/ui'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { getProjectConfig, - getProjectDetailUrl, validateProjectConfig, } = require('../../lib/projects'); +const { getProjectDetailUrl } = require('../../lib/projects/urls'); const moment = require('moment'); const { promptUser } = require('../../lib/prompts/promptUtils'); const { isHubSpotHttpError } = require('@hubspot/local-dev-lib/errors/index'); const i18nKey = 'commands.project.subcommands.listBuilds'; -exports.command = 'list-builds [path]'; +exports.command = 'list-builds'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - - const { path: projectPath, limit } = options; - const accountId = getAccountId(options); - - trackCommandUsage('project-list-builds', null, accountId); + const { project: projectFlagValue, limit, derivedAccountId } = options; - const cwd = projectPath ? path.resolve(getCwd(), projectPath) : getCwd(); - const { projectConfig, projectDir } = await getProjectConfig(cwd); + trackCommandUsage('project-list-builds', null, derivedAccountId); - validateProjectConfig(projectConfig, projectDir); + let projectName = projectFlagValue; - logger.debug(`Fetching builds for project at path: ${projectPath}`); + if (!projectName) { + const { projectConfig, projectDir } = await getProjectConfig(); + validateProjectConfig(projectConfig, projectDir); + projectName = projectConfig.name; + } const fetchAndDisplayBuilds = async (project, options) => { const { data: { results, paging }, - } = await fetchProjectBuilds(accountId, project.name, options); + } = await fetchProjectBuilds(derivedAccountId, project.name, options); const currentDeploy = project.deployedBuildId; if (options && options.after) { logger.log( - `Showing the next ${results.length} builds for ${project.name}` + i18n(`${i18nKey}.logs.showingNextBuilds`, { + count: results.length, + projectName: project.name, + }) ); } else { logger.log( - `Showing the ${results.length} most recent builds for ${project.name}. ` + - uiLink( - 'View all builds in project details.', - getProjectDetailUrl(project.name, accountId) - ) + i18n(`${i18nKey}.logs.showingRecentBuilds`, { + count: results.length, + projectName: project.name, + viewBuildsLink: uiLink( + i18n(`${i18nKey}.logs.viewAllBuildsLink`), + getProjectDetailUrl(project.name, derivedAccountId) + ), + }) ); } if (results.length === 0) { - logger.log('No builds found.'); + logger.log(i18n(`${i18nKey}.errors.noBuilds`)); } else { const builds = results.map(build => { const isCurrentlyDeployed = build.buildId === currentDeploy; @@ -110,46 +109,45 @@ exports.handler = async options => { if (paging && paging.next) { await promptUser({ name: 'more', - message: 'Press to load more, or ctrl+c to exit', + message: i18n(`${i18nKey}.continueOrExitPrompt`), }); await fetchAndDisplayBuilds(project, { limit, after: paging.next.after }); } }; try { - const { data: project } = await fetchProject(accountId, projectConfig.name); + const { data: project } = await fetchProject(derivedAccountId, projectName); await fetchAndDisplayBuilds(project, { limit }); } catch (e) { if (isHubSpotHttpError(e) && e.status === 404) { - logger.error(`Project ${projectConfig.name} not found. `); + logger.error(i18n(`${i18nKey}.errors.projectNotFound`, { projectName })); } else { logError( e, - new ApiErrorContext({ accountId, projectName: projectConfig.name }) + new ApiErrorContext({ + accountId: derivedAccountId, + projectName, + }) ); } } }; exports.builder = yargs => { - yargs.positional('path', { - describe: 'Path to a project folder', - type: 'string', - }); - yargs.options({ + project: { + describe: i18n(`${i18nKey}.options.project.describe`), + type: 'string', + }, limit: { - describe: 'Max number of builds to load', + describe: i18n(`${i18nKey}.options.limit.describe`), type: 'string', }, }); yargs.example([ - [ - '$0 project list-builds myProjectFolder', - 'Fetch a list of builds for a project within the myProjectFolder folder', - ], + ['$0 project list-builds', i18n(`${i18nKey}.examples.default`)], ]); addConfigOptions(yargs); diff --git a/commands/project/logs.ts b/commands/project/logs.ts index 33779d5bc..78ed0ee1e 100644 --- a/commands/project/logs.ts +++ b/commands/project/logs.ts @@ -4,21 +4,17 @@ const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); const { ENVIRONMENTS, } = require('@hubspot/local-dev-lib/constants/environments'); -const { - getAccountId, - addUseEnvironmentOptions, -} = require('../../lib/commonOpts'); +const { addUseEnvironmentOptions } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { getTableContents, getTableHeader } = require('../../lib/ui/table'); const { logError } = require('../../lib/errorHandlers/'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { uiBetaTag, uiLine, uiLink } = require('../../lib/ui'); const { projectLogsPrompt } = require('../../lib/prompts/projectsLogsPrompt'); const { i18n } = require('../../lib/lang'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); -const ProjectLogsManager = require('../../lib/ProjectLogsManager'); +const { ProjectLogsManager } = require('../../lib/projects/ProjectLogsManager'); const i18nKey = 'commands.project.subcommands.logs'; @@ -86,13 +82,11 @@ exports.command = 'logs'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - const accountId = getAccountId(options); - trackCommandUsage('project-logs', null, accountId); - - await loadAndValidateOptions(options); + const { derivedAccountId } = options; + trackCommandUsage('project-logs', null, derivedAccountId); try { - await ProjectLogsManager.init(accountId); + await ProjectLogsManager.init(derivedAccountId); const { functionName } = await projectLogsPrompt({ functionChoices: ProjectLogsManager.getFunctionNames(), @@ -105,7 +99,7 @@ exports.handler = async options => { logPreamble(); } catch (e) { logError(e, { - accountId, + accountId: derivedAccountId, projectName: ProjectLogsManager.projectName, }); return process.exit(EXIT_CODES.ERROR); diff --git a/commands/project/migrateApp.ts b/commands/project/migrateApp.ts index 0068827b1..85b893d18 100644 --- a/commands/project/migrateApp.ts +++ b/commands/project/migrateApp.ts @@ -3,14 +3,12 @@ const path = require('path'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage, trackCommandMetadataUsage, } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { createProjectPrompt, } = require('../../lib/prompts/createProjectPrompt'); @@ -53,13 +51,11 @@ exports.command = 'migrate-app'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); + const { derivedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); + const accountName = uiAccountDescription(derivedAccountId); - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); - const accountName = uiAccountDescription(accountId); - - trackCommandUsage('migrate-app', {}, accountId); + trackCommandUsage('migrate-app', {}, derivedAccountId); logger.log(''); logger.log(uiBetaTag(i18n(`${i18nKey}.header.text`), false)); @@ -88,7 +84,7 @@ exports.handler = async options => { 'appId' in options ? options : await selectPublicAppPrompt({ - accountId, + accountId: derivedAccountId, accountName, isMigratingApp: true, }); @@ -96,7 +92,7 @@ exports.handler = async options => { try { const { data: selectedApp } = await fetchPublicAppMetadata( appId, - accountId + derivedAccountId ); // preventProjectMigrations returns true if we have not added app to allowlist config. // listingInfo will only exist for marketplace apps @@ -107,20 +103,20 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } } catch (error) { - logError(error, new ApiErrorContext({ accountId })); + logError(error, new ApiErrorContext({ accountId: derivedAccountId })); process.exit(EXIT_CODES.ERROR); } let projectName; - let projectLocation; + let projectDest; try { - const { name, location } = await createProjectPrompt('', options, true); + const { name, dest } = await createProjectPrompt('', options, true); projectName = options.name || name; - projectLocation = options.location || location; + projectDest = options.dest || dest; const { projectExists } = await ensureProjectExists( - accountId, + derivedAccountId, projectName, { allowCreate: false, @@ -137,10 +133,16 @@ exports.handler = async options => { process.exit(EXIT_CODES.ERROR); } } catch (error) { - logError(error, new ApiErrorContext({ accountId })); + logError(error, new ApiErrorContext({ accountId: derivedAccountId })); process.exit(EXIT_CODES.ERROR); } + await trackCommandMetadataUsage( + 'migrate-app', + { status: 'STARTED' }, + derivedAccountId + ); + logger.log(''); uiLine(); logger.warn(i18n(`${i18nKey}.warning.title`)); @@ -182,20 +184,20 @@ exports.handler = async options => { }); const { data: migrateResponse } = await migrateApp( - accountId, + derivedAccountId, appId, projectName ); const { id } = migrateResponse; - const pollResponse = await poll(checkMigrationStatus, accountId, id); + const pollResponse = await poll(checkMigrationStatus, derivedAccountId, id); const { status, project } = pollResponse; if (status === 'SUCCESS') { - const absoluteDestPath = path.resolve(getCwd(), projectLocation); - const { env } = getAccountConfig(accountId); + const absoluteDestPath = path.resolve(getCwd(), projectDest); + const { env } = accountConfig; const baseUrl = getHubSpotWebsiteOrigin(env); const { data: zippedProject } = await downloadProject( - accountId, + derivedAccountId, projectName, 1 ); @@ -207,12 +209,6 @@ exports.handler = async options => { { includesRootDir: true, hideLogs: true } ); - trackCommandMetadataUsage( - 'migrate-app', - { type: projectName, assetType: appId, successful: status }, - accountId - ); - SpinniesManager.succeed('migrateApp', { text: i18n(`${i18nKey}.migrationStatus.done`), succeedColor: 'white', @@ -224,7 +220,7 @@ exports.handler = async options => { logger.log( uiLink( i18n(`${i18nKey}.projectDetailsLink`), - `${baseUrl}/developer-projects/${accountId}/project/${encodeURIComponent( + `${baseUrl}/developer-projects/${derivedAccountId}/project/${encodeURIComponent( project.name )}` ) @@ -232,10 +228,10 @@ exports.handler = async options => { process.exit(EXIT_CODES.SUCCESS); } } catch (error) { - trackCommandMetadataUsage( + await trackCommandMetadataUsage( 'migrate-app', - { projectName, appId, status: 'FAILURE', error }, - accountId + { status: 'FAILURE' }, + derivedAccountId ); SpinniesManager.fail('migrateApp', { text: i18n(`${i18nKey}.migrationStatus.failure`), @@ -244,11 +240,17 @@ exports.handler = async options => { if (error.errors) { error.errors.forEach(logError); } else { - logError(error, new ApiErrorContext({ accountId })); + logError(error, new ApiErrorContext({ accountId: derivedAccountId })); } process.exit(EXIT_CODES.ERROR); } + await trackCommandMetadataUsage( + 'migrate-app', + { status: 'SUCCESS' }, + derivedAccountId + ); + process.exit(EXIT_CODES.SUCCESS); }; exports.builder = yargs => { @@ -257,11 +259,11 @@ exports.builder = yargs => { describe: i18n(`${i18nKey}.options.name.describe`), type: 'string', }, - location: { - describe: i18n(`${i18nKey}.options.location.describe`), + dest: { + describe: i18n(`${i18nKey}.options.dest.describe`), type: 'string', }, - appId: { + 'app-id': { describe: i18n(`${i18nKey}.options.appId.describe`), type: 'number', }, diff --git a/commands/project/open.ts b/commands/project/open.ts index 08fd80d56..351d5fed5 100644 --- a/commands/project/open.ts +++ b/commands/project/open.ts @@ -3,35 +3,27 @@ const open = require('open'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, addTestingOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - getProjectConfig, - getProjectDetailUrl, - ensureProjectExists, -} = require('../../lib/projects'); +const { getProjectConfig, ensureProjectExists } = require('../../lib/projects'); +const { getProjectDetailUrl } = require('../../lib/projects/urls'); const { projectNamePrompt } = require('../../lib/prompts/projectNamePrompt'); const { uiBetaTag } = require('../../lib/ui'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const i18nKey = 'commands.project.subcommands.open'; -exports.command = 'open [--project]'; +exports.command = 'open'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - const { project } = options; + const { project, derivedAccountId } = options; - trackCommandUsage('project-open', null, accountId); + trackCommandUsage('project-open', null, derivedAccountId); const { projectConfig } = await getProjectConfig(); @@ -39,7 +31,7 @@ exports.handler = async options => { if (projectName) { const { projectExists } = await ensureProjectExists( - accountId, + derivedAccountId, projectName, { allowCreate: false, @@ -52,7 +44,7 @@ exports.handler = async options => { } else if (!projectName && projectConfig) { projectName = projectConfig.name; } else if (!projectName && !projectConfig) { - const namePrompt = await projectNamePrompt(accountId); + const namePrompt = await projectNamePrompt(derivedAccountId); if (!namePrompt.projectName) { process.exit(EXIT_CODES.ERROR); @@ -60,7 +52,7 @@ exports.handler = async options => { projectName = namePrompt.projectName; } - const url = getProjectDetailUrl(projectName, accountId); + const url = getProjectDetailUrl(projectName, derivedAccountId); open(url, { url: true }); logger.success(i18n(`${i18nKey}.success`, { projectName })); process.exit(EXIT_CODES.SUCCESS); diff --git a/commands/project/upload.ts b/commands/project/upload.ts index 745c195b4..343e93d52 100644 --- a/commands/project/upload.ts +++ b/commands/project/upload.ts @@ -2,23 +2,23 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const chalk = require('chalk'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { uiBetaTag, uiCommandReference } = require('../../lib/ui'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { ensureProjectExists, getProjectConfig, - handleProjectUpload, logFeedbackMessage, validateProjectConfig, - pollProjectBuildAndDeploy, - displayWarnLogs, } = require('../../lib/projects'); +const { handleProjectUpload } = require('../../lib/projects/upload'); +const { + displayWarnLogs, + pollProjectBuildAndDeploy, +} = require('../../lib/projects/buildAndDeploy'); const { i18n } = require('../../lib/lang'); const { getAccountConfig } = require('@hubspot/local-dev-lib/config'); const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); @@ -28,31 +28,28 @@ const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const i18nKey = 'commands.project.subcommands.upload'; -exports.command = 'upload [path] [--forceCreate] [--message]'; +exports.command = 'upload'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); - - const { forceCreate, path: projectPath, message } = options; - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); + const { forceCreate, message, derivedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); const accountType = accountConfig && accountConfig.accountType; - trackCommandUsage('project-upload', { type: accountType }, accountId); + trackCommandUsage('project-upload', { type: accountType }, derivedAccountId); - const { projectConfig, projectDir } = await getProjectConfig(projectPath); + const { projectConfig, projectDir } = await getProjectConfig(); validateProjectConfig(projectConfig, projectDir); - await ensureProjectExists(accountId, projectConfig.name, { + await ensureProjectExists(derivedAccountId, projectConfig.name, { forceCreate, uploadCommand: true, }); try { const result = await handleProjectUpload( - accountId, + derivedAccountId, projectConfig, projectDir, pollProjectBuildAndDeploy, @@ -72,7 +69,7 @@ exports.handler = async options => { logError( result.uploadError, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: 'project upload', }) ); @@ -96,37 +93,41 @@ exports.handler = async options => { ); logFeedbackMessage(result.buildId); - await displayWarnLogs(accountId, projectConfig.name, result.buildId); + await displayWarnLogs( + derivedAccountId, + projectConfig.name, + result.buildId + ); process.exit(EXIT_CODES.SUCCESS); } } catch (e) { - logError(e, new ApiErrorContext({ accountId, request: 'project upload' })); + logError( + e, + new ApiErrorContext({ + accountId: derivedAccountId, + request: 'project upload', + }) + ); process.exit(EXIT_CODES.ERROR); } }; exports.builder = yargs => { - yargs.positional('path', { - describe: i18n(`${i18nKey}.positionals.path.describe`), - type: 'string', - }); - - yargs.option('forceCreate', { - describe: i18n(`${i18nKey}.options.forceCreate.describe`), - type: 'boolean', - default: false, - }); - - yargs.option('message', { - alias: 'm', - describe: i18n(`${i18nKey}.options.message.describe`), - type: 'string', - default: '', + yargs.options({ + 'force-create': { + describe: i18n(`${i18nKey}.options.forceCreate.describe`), + type: 'boolean', + default: false, + }, + message: { + alias: 'm', + describe: i18n(`${i18nKey}.options.message.describe`), + type: 'string', + default: '', + }, }); - yargs.example([ - ['$0 project upload myProjectFolder', i18n(`${i18nKey}.examples.default`)], - ]); + yargs.example([['$0 project upload', i18n(`${i18nKey}.examples.default`)]]); addConfigOptions(yargs); addAccountOptions(yargs); diff --git a/commands/project/watch.ts b/commands/project/watch.ts index cdddf545e..ceac52615 100644 --- a/commands/project/watch.ts +++ b/commands/project/watch.ts @@ -1,13 +1,12 @@ // @ts-nocheck const { i18n } = require('../../lib/lang'); -const { createWatcher } = require('../../lib/projectsWatch'); +const { createWatcher } = require('../../lib/projects/watch'); const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { PROJECT_ERROR_TYPES } = require('../../lib/constants'); const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, } = require('../../lib/commonOpts'); const { trackCommandUsage } = require('../../lib/usageTracking'); @@ -15,31 +14,30 @@ const { uiBetaTag } = require('../../lib/ui'); const { ensureProjectExists, getProjectConfig, - handleProjectUpload, - pollBuildStatus, - pollDeployStatus, validateProjectConfig, logFeedbackMessage, } = require('../../lib/projects'); +const { handleProjectUpload } = require('../../lib/projects/upload'); +const { + pollBuildStatus, + pollDeployStatus, +} = require('../../lib/projects/buildAndDeploy'); const { cancelStagedBuild, fetchProjectBuilds, } = require('@hubspot/local-dev-lib/api/projects'); const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { handleKeypress, handleExit } = require('../../lib/process'); const i18nKey = 'commands.project.subcommands.watch'; -exports.command = 'watch [path]'; +exports.command = 'watch'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); const handleBuildStatus = async (accountId, projectName, buildId) => { - const { - isAutoDeployEnabled, - deployStatusTaskLocator, - } = await pollBuildStatus(accountId, projectName, buildId); + const { isAutoDeployEnabled, deployStatusTaskLocator } = + await pollBuildStatus(accountId, projectName, buildId); if (isAutoDeployEnabled && deployStatusTaskLocator) { await pollDeployStatus( @@ -87,28 +85,25 @@ const handleUserInput = (accountId, projectName, currentBuildId) => { }; exports.handler = async options => { - await loadAndValidateOptions(options); - - const { initialUpload, path: projectPath } = options; - const accountId = getAccountId(options); + const { initialUpload, derivedAccountId } = options; - trackCommandUsage('project-watch', null, accountId); + trackCommandUsage('project-watch', null, derivedAccountId); - const { projectConfig, projectDir } = await getProjectConfig(projectPath); + const { projectConfig, projectDir } = await getProjectConfig(); validateProjectConfig(projectConfig, projectDir); - await ensureProjectExists(accountId, projectConfig.name); + await ensureProjectExists(derivedAccountId, projectConfig.name); try { const { data: { results: builds }, - } = await fetchProjectBuilds(accountId, projectConfig.name, options); + } = await fetchProjectBuilds(derivedAccountId, projectConfig.name, options); const hasNoBuilds = !builds || !builds.length; const startWatching = async () => { await createWatcher( - accountId, + derivedAccountId, projectConfig, projectDir, handleBuildStatus, @@ -119,7 +114,7 @@ exports.handler = async options => { // Upload all files if no build exists for this project yet if (initialUpload || hasNoBuilds) { const result = await handleProjectUpload( - accountId, + derivedAccountId, projectConfig, projectDir, startWatching @@ -138,7 +133,7 @@ exports.handler = async options => { logError( result.uploadError, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: 'project upload', }) ); @@ -149,25 +144,18 @@ exports.handler = async options => { await startWatching(); } } catch (e) { - logError(e, new ApiErrorContext({ accountId })); + logError(e, new ApiErrorContext({ accountId: derivedAccountId })); } }; exports.builder = yargs => { - yargs.positional('path', { - describe: i18n(`${i18nKey}.positionals.path.describe`), - type: 'string', - }); - yargs.option('initial-upload', { alias: 'i', describe: i18n(`${i18nKey}.options.initialUpload.describe`), type: 'boolean', }); - yargs.example([ - ['$0 project watch myProjectFolder', i18n(`${i18nKey}.examples.default`)], - ]); + yargs.example([['$0 project watch', i18n(`${i18nKey}.examples.default`)]]); addConfigOptions(yargs); addAccountOptions(yargs); diff --git a/commands/remove.ts b/commands/remove.ts index 996533d1e..3d2c0a711 100644 --- a/commands/remove.ts +++ b/commands/remove.ts @@ -7,9 +7,8 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, + addGlobalOptions, } = require('../lib/commonOpts'); -const { loadAndValidateOptions } = require('../lib/validation'); const { trackCommandUsage } = require('../lib/usageTracking'); const { i18n } = require('../lib/lang'); @@ -19,25 +18,26 @@ exports.command = 'remove '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { path: hsPath } = options; + const { path: hsPath, derivedAccountId } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - - trackCommandUsage('remove', null, accountId); + trackCommandUsage('remove', null, derivedAccountId); try { - await deleteFile(accountId, hsPath); - logger.log(i18n(`${i18nKey}.deleted`, { accountId, path: hsPath })); + await deleteFile(derivedAccountId, hsPath); + logger.log( + i18n(`${i18nKey}.deleted`, { accountId: derivedAccountId, path: hsPath }) + ); } catch (error) { logger.error( - i18n(`${i18nKey}.errors.deleteFailed`, { accountId, path: hsPath }) + i18n(`${i18nKey}.errors.deleteFailed`, { + accountId: derivedAccountId, + path: hsPath, + }) ); logError( error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: hsPath, }) ); @@ -45,12 +45,15 @@ exports.handler = async options => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); yargs.positional('path', { describe: i18n(`${i18nKey}.positionals.path.describe`), type: 'string', }); + + addConfigOptions(yargs); + addAccountOptions(yargs); + addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); + return yargs; }; diff --git a/commands/sandbox.ts b/commands/sandbox.ts index 0df69fdbf..0dc643247 100644 --- a/commands/sandbox.ts +++ b/commands/sandbox.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); +const { addGlobalOptions } = require('../lib/commonOpts'); const { i18n } = require('../lib/lang'); const { uiBetaTag } = require('../lib/ui'); const create = require('./sandbox/create'); @@ -7,17 +7,13 @@ const del = require('./sandbox/delete'); const i18nKey = 'commands.sandbox'; -exports.command = 'sandbox'; +exports.command = ['sandbox', 'sandboxes']; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); + addGlobalOptions(yargs); - yargs - .command(create) - .command(del) - .demandCommand(1, ''); + yargs.command(create).command(del).demandCommand(1, ''); return yargs; }; diff --git a/commands/sandbox/create.ts b/commands/sandbox/create.ts index 7a63ba466..b516e307d 100644 --- a/commands/sandbox/create.ts +++ b/commands/sandbox/create.ts @@ -2,11 +2,9 @@ const { addAccountOptions, addConfigOptions, - getAccountId, addUseEnvironmentOptions, addTestingOptions, } = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { i18n } = require('../../lib/lang'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { getAccountConfig, getEnv } = require('@hubspot/local-dev-lib/config'); @@ -37,18 +35,15 @@ const { const i18nKey = 'commands.sandbox.subcommands.create'; -exports.command = 'create [--name] [--type]'; +exports.command = 'create'; exports.describe = uiBetaTag(i18n(`${i18nKey}.describe`), false); exports.handler = async options => { - await loadAndValidateOptions(options); + const { name, type, force, derivedAccountId } = options; + const accountConfig = getAccountConfig(derivedAccountId); + const env = getValidEnv(getEnv(derivedAccountId)); - const { name, type, force } = options; - const accountId = getAccountId(options); - const accountConfig = getAccountConfig(accountId); - const env = getValidEnv(getEnv(accountId)); - - trackCommandUsage('sandbox-create', null, accountId); + trackCommandUsage('sandbox-create', null, derivedAccountId); // Default account is not a production portal if ( @@ -89,14 +84,14 @@ exports.handler = async options => { if (isMissingScopeError(err)) { logger.error( i18n('lib.sandbox.create.failure.scopes.message', { - accountName: accountConfig.name || accountId, + accountName: accountConfig.name || derivedAccountId, }) ); const websiteOrigin = getHubSpotWebsiteOrigin(env); - const url = `${websiteOrigin}/personal-access-key/${accountId}`; + const url = `${websiteOrigin}/personal-access-key/${derivedAccountId}`; logger.info( i18n('lib.sandbox.create.failure.scopes.instructions', { - accountName: accountConfig.name || accountId, + accountName: accountConfig.name || derivedAccountId, url, }) ); @@ -186,10 +181,10 @@ exports.handler = async options => { }; exports.builder = yargs => { - yargs.option('f', { + yargs.option('force', { type: 'boolean', - alias: 'force', - describe: i18n(`${i18nKey}.examples.force`), + alias: 'f', + describe: i18n(`${i18nKey}.options.force.describe`), }); yargs.option('name', { describe: i18n(`${i18nKey}.options.name.describe`), diff --git a/commands/sandbox/delete.ts b/commands/sandbox/delete.ts index f065eb646..290acd49d 100644 --- a/commands/sandbox/delete.ts +++ b/commands/sandbox/delete.ts @@ -8,46 +8,46 @@ const { } = require('../../lib/commonOpts'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { logError, debugError } = require('../../lib/errorHandlers/index'); const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); const { deleteSandbox } = require('@hubspot/local-dev-lib/api/sandboxHubs'); const { i18n } = require('../../lib/lang'); const { deleteSandboxPrompt } = require('../../lib/prompts/sandboxesPrompt'); const { - getConfig, getEnv, removeSandboxAccountFromConfig, updateDefaultAccount, + getConfigDefaultAccount, + getConfigAccounts, } = require('@hubspot/local-dev-lib/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); const { selectAccountFromConfig } = require('../../lib/prompts/accountsPrompt'); const { EXIT_CODES } = require('../../lib/enums/exitCodes'); const { promptUser } = require('../../lib/prompts/promptUtils'); +const { uiAccountDescription, uiBetaTag } = require('../../lib/ui'); const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); const { getValidEnv } = require('@hubspot/local-dev-lib/environment'); -const { uiAccountDescription, uiBetaTag } = require('../../lib/ui'); const i18nKey = 'commands.sandbox.subcommands.delete'; -exports.command = 'delete [--account]'; +exports.command = 'delete'; exports.describe = exports.describe = uiBetaTag( i18n(`${i18nKey}.describe`), false ); exports.handler = async options => { - await loadAndValidateOptions(options, false); - - const { account, force } = options; - const config = getConfig(); + const { providedAccountId, force } = options; trackCommandUsage('sandbox-delete', null); let accountPrompt; - if (!account) { + if (!providedAccountId) { if (!force) { - accountPrompt = await deleteSandboxPrompt(config); + accountPrompt = await deleteSandboxPrompt(); } else { // Account is required, throw error if force flag is present and no account is specified logger.log(''); @@ -62,22 +62,23 @@ exports.handler = async options => { } const sandboxAccountId = getAccountId({ - account: account || accountPrompt.account, + account: providedAccountId || accountPrompt.account, }); const isDefaultAccount = - sandboxAccountId === getAccountId(config.defaultPortal); + sandboxAccountId === getAccountId(getConfigDefaultAccount()); const baseUrl = getHubSpotWebsiteOrigin( getValidEnv(getEnv(sandboxAccountId)) ); let parentAccountId; - for (const portal of config.portals) { - if (portal.portalId === sandboxAccountId) { + const accountsList = getConfigAccounts(); + for (const portal of accountsList) { + if (getAccountIdentifier(portal) === sandboxAccountId) { if (portal.parentAccountId) { parentAccountId = portal.parentAccountId; } else if (!force) { - const parentAccountPrompt = await deleteSandboxPrompt(config, true); + const parentAccountPrompt = await deleteSandboxPrompt(true); parentAccountId = getAccountId({ account: parentAccountPrompt.account, }); @@ -145,17 +146,16 @@ exports.handler = async options => { logger.log(''); logger.success( i18n(deleteKey, { - account: account || accountPrompt.account, + account: providedAccountId || accountPrompt.account, sandboxHubId: sandboxAccountId, }) ); logger.log(''); - const promptDefaultAccount = removeSandboxAccountFromConfig( - sandboxAccountId - ); + const promptDefaultAccount = + removeSandboxAccountFromConfig(sandboxAccountId); if (promptDefaultAccount && !force) { - const newDefaultAccount = await selectAccountFromConfig(getConfig()); + const newDefaultAccount = await selectAccountFromConfig(); updateDefaultAccount(newDefaultAccount); } else { // If force is specified, skip prompt and set the parent account id as the default account @@ -204,11 +204,10 @@ exports.handler = async options => { ); logger.log(''); - const promptDefaultAccount = removeSandboxAccountFromConfig( - sandboxAccountId - ); + const promptDefaultAccount = + removeSandboxAccountFromConfig(sandboxAccountId); if (promptDefaultAccount && !force) { - const newDefaultAccount = await selectAccountFromConfig(getConfig()); + const newDefaultAccount = await selectAccountFromConfig(); updateDefaultAccount(newDefaultAccount); } else { // If force is specified, skip prompt and set the parent account id as the default account @@ -227,10 +226,10 @@ exports.builder = yargs => { describe: i18n(`${i18nKey}.options.account.describe`), type: 'string', }); - yargs.option('f', { + yargs.option('force', { type: 'boolean', - alias: 'force', - describe: i18n(`${i18nKey}.examples.force`), + alias: 'f', + describe: i18n(`${i18nKey}.options.force.describe`), }); yargs.example([ diff --git a/commands/secret.ts b/commands/secret.ts new file mode 100644 index 000000000..74fb33576 --- /dev/null +++ b/commands/secret.ts @@ -0,0 +1,25 @@ +// @ts-nocheck +const { addGlobalOptions } = require('../lib/commonOpts'); + +const addSecretCommand = require('./secret/addSecret'); +const listSecretsCommand = require('./secret/listSecrets'); +const deleteSecretCommand = require('./secret/deleteSecret'); +const updateSecretCommand = require('./secret/updateSecret'); +const { i18n } = require('../lib/lang'); + +const i18nKey = 'commands.secret'; + +exports.command = ['secret', 'secrets']; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.builder = yargs => { + addGlobalOptions(yargs); + + yargs + .command(listSecretsCommand) + .command(addSecretCommand) + .command(updateSecretCommand) + .command(deleteSecretCommand) + .demandCommand(1, ''); + return yargs; +}; diff --git a/commands/secrets/addSecret.ts b/commands/secret/addSecret.ts similarity index 52% rename from commands/secrets/addSecret.ts rename to commands/secret/addSecret.ts index 8d96f6a99..1d57d7db9 100644 --- a/commands/secrets/addSecret.ts +++ b/commands/secret/addSecret.ts @@ -1,41 +1,62 @@ // @ts-nocheck +import { EXIT_CODES } from '../../lib/enums/exitCodes'; +import { fetchSecrets } from '@hubspot/local-dev-lib/api/secrets'; +import { uiCommandReference } from '../../lib/ui'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index'); const { addSecret } = require('@hubspot/local-dev-lib/api/secrets'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); - const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { uiAccountDescription } = require('../../lib/ui'); -const { secretValuePrompt } = require('../../lib/prompts/secretPrompt'); +const { + secretValuePrompt, + secretNamePrompt, +} = require('../../lib/prompts/secretPrompt'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.secrets.subcommands.add'; +const i18nKey = 'commands.secret.subcommands.add'; -exports.command = 'add '; +exports.command = 'add [name]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { name: secretName } = options; - - await loadAndValidateOptions(options); + const { name, derivedAccountId } = options; + let secretName = name; - const accountId = getAccountId(options); - trackCommandUsage('secrets-add', null, accountId); + trackCommandUsage('secrets-add', null, derivedAccountId); try { + if (!secretName) { + const { secretName: name } = await secretNamePrompt(); + secretName = name; + } + + const { + data: { results: secrets }, + } = await fetchSecrets(derivedAccountId); + + if (secrets.includes(secretName)) { + logger.error( + i18n(`${i18nKey}.errors.alreadyExists`, { + secretName, + command: uiCommandReference('hs secret update'), + }) + ); + process.exit(EXIT_CODES.ERROR); + } + const { secretValue } = await secretValuePrompt(); - await addSecret(accountId, secretName, secretValue); + await addSecret(derivedAccountId, secretName, secretValue); logger.success( i18n(`${i18nKey}.success.add`, { - accountIdentifier: uiAccountDescription(accountId), + accountIdentifier: uiAccountDescription(derivedAccountId), secretName, }) ); @@ -49,7 +70,7 @@ exports.handler = async options => { err, new ApiErrorContext({ request: 'add secret', - accountId, + accountId: derivedAccountId, }) ); } diff --git a/commands/secret/deleteSecret.ts b/commands/secret/deleteSecret.ts new file mode 100644 index 000000000..22fda3cbf --- /dev/null +++ b/commands/secret/deleteSecret.ts @@ -0,0 +1,102 @@ +// @ts-nocheck +import { secretListPrompt } from '../../lib/prompts/secretPrompt'; +import { confirmPrompt } from '../../lib/prompts/promptUtils'; +import { EXIT_CODES } from '../../lib/enums/exitCodes'; + +const { logger } = require('@hubspot/local-dev-lib/logger'); +const { ApiErrorContext, logError } = require('../../lib/errorHandlers/index'); +const { + deleteSecret, + fetchSecrets, +} = require('@hubspot/local-dev-lib/api/secrets'); + +const { trackCommandUsage } = require('../../lib/usageTracking'); +const { uiAccountDescription } = require('../../lib/ui'); + +const { + addConfigOptions, + addAccountOptions, + addUseEnvironmentOptions, +} = require('../../lib/commonOpts'); +const { i18n } = require('../../lib/lang'); + +const i18nKey = 'commands.secret.subcommands.delete'; + +exports.command = 'delete [name]'; +exports.describe = i18n(`${i18nKey}.describe`); + +exports.handler = async options => { + const { name, derivedAccountId, force } = options; + let secretName = name; + + trackCommandUsage('secrets-delete', null, derivedAccountId); + + try { + const { + data: { results: secrets }, + } = await fetchSecrets(derivedAccountId); + + if (secretName && !secrets.includes(secretName)) { + logger.error(i18n(`${i18nKey}.errors.noSecret`, { secretName })); + process.exit(EXIT_CODES.ERROR); + } + + if (!secretName) { + const { secretToModify } = await secretListPrompt( + secrets, + i18n(`${i18nKey}.selectSecret`) + ); + secretName = secretToModify; + } + + const confirmDelete = + force || + (await confirmPrompt(i18n(`${i18nKey}.confirmDelete`, { secretName }), { + defaultAnswer: false, + })); + + if (!confirmDelete) { + logger.success(i18n(`${i18nKey}.deleteCanceled`)); + process.exit(EXIT_CODES.SUCCESS); + } + + await deleteSecret(derivedAccountId, secretName); + logger.success( + i18n(`${i18nKey}.success.delete`, { + accountIdentifier: uiAccountDescription(derivedAccountId), + secretName, + }) + ); + } catch (err) { + if (secretName) { + logger.error( + i18n(`${i18nKey}.errors.delete`, { + secretName, + }) + ); + } + logError( + err, + new ApiErrorContext({ + request: 'delete a secret', + accountId: derivedAccountId, + }) + ); + } +}; + +exports.builder = yargs => { + addConfigOptions(yargs); + addAccountOptions(yargs); + addUseEnvironmentOptions(yargs); + yargs + .positional('name', { + describe: i18n(`${i18nKey}.positionals.name.describe`), + type: 'string', + }) + .options('force', { + describe: 'Force the deletion', + type: 'boolean', + }); + return yargs; +}; diff --git a/commands/secrets/listSecrets.ts b/commands/secret/listSecrets.ts similarity index 74% rename from commands/secrets/listSecrets.ts rename to commands/secret/listSecrets.ts index 75cccd7c8..2f8f5d428 100644 --- a/commands/secrets/listSecrets.ts +++ b/commands/secret/listSecrets.ts @@ -3,7 +3,6 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { logError, ApiErrorContext } = require('../../lib/errorHandlers/index'); const { fetchSecrets } = require('@hubspot/local-dev-lib/api/secrets'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { uiAccountDescription } = require('../../lib/ui'); @@ -11,27 +10,24 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.secrets.subcommands.list'; +const i18nKey = 'commands.secret.subcommands.list'; exports.command = 'list'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - trackCommandUsage('secrets-list', null, accountId); + const { derivedAccountId } = options; + trackCommandUsage('secrets-list', null, derivedAccountId); try { const { data: { results }, - } = await fetchSecrets(accountId); + } = await fetchSecrets(derivedAccountId); const groupLabel = i18n(`${i18nKey}.groupLabel`, { - accountIdentifier: uiAccountDescription(accountId), + accountIdentifier: uiAccountDescription(derivedAccountId), }); logger.group(groupLabel); results.forEach(secret => logger.log(secret)); @@ -42,7 +38,7 @@ exports.handler = async options => { err, new ApiErrorContext({ request: 'add secret', - accountId, + accountId: derivedAccountId, }) ); } diff --git a/commands/secrets/updateSecret.ts b/commands/secret/updateSecret.ts similarity index 53% rename from commands/secrets/updateSecret.ts rename to commands/secret/updateSecret.ts index 01b84c7a8..51666fc36 100644 --- a/commands/secrets/updateSecret.ts +++ b/commands/secret/updateSecret.ts @@ -1,9 +1,13 @@ // @ts-nocheck +import { EXIT_CODES } from '../../lib/enums/exitCodes'; + const { logger } = require('@hubspot/local-dev-lib/logger'); const { ApiErrorContext, logError } = require('../../lib/errorHandlers/index'); -const { updateSecret } = require('@hubspot/local-dev-lib/api/secrets'); +const { + updateSecret, + fetchSecrets, +} = require('@hubspot/local-dev-lib/api/secrets'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { uiAccountDescription } = require('../../lib/ui'); @@ -11,31 +15,48 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); -const { secretValuePrompt } = require('../../lib/prompts/secretPrompt'); +const { + secretValuePrompt, + secretListPrompt, +} = require('../../lib/prompts/secretPrompt'); const { i18n } = require('../../lib/lang'); -const i18nKey = 'commands.secrets.subcommands.update'; +const i18nKey = 'commands.secret.subcommands.update'; -exports.command = 'update '; +exports.command = 'update [name]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { name: secretName } = options; - - await loadAndValidateOptions(options); + const { name, derivedAccountId } = options; + let secretName = name; - const accountId = getAccountId(options); - trackCommandUsage('secrets-update', null, accountId); + trackCommandUsage('secrets-update', null, derivedAccountId); try { + const { + data: { results: secrets }, + } = await fetchSecrets(derivedAccountId); + + if (secretName && !secrets.includes(secretName)) { + logger.error(i18n(`${i18nKey}.errors.noSecret`, { secretName })); + process.exit(EXIT_CODES.ERROR); + } + + if (!secretName) { + const { secretToModify } = await secretListPrompt( + secrets, + i18n(`${i18nKey}.selectSecret`) + ); + secretName = secretToModify; + } + const { secretValue } = await secretValuePrompt(); - await updateSecret(accountId, secretName, secretValue); + await updateSecret(derivedAccountId, secretName, secretValue); logger.success( i18n(`${i18nKey}.success.update`, { - accountIdentifier: uiAccountDescription(accountId), + accountIdentifier: uiAccountDescription(derivedAccountId), secretName, }) ); @@ -50,7 +71,7 @@ exports.handler = async options => { err, new ApiErrorContext({ request: 'update secret', - accountId, + accountId: derivedAccountId, }) ); } diff --git a/commands/secrets.ts b/commands/secrets.ts deleted file mode 100644 index 09b9c918d..000000000 --- a/commands/secrets.ts +++ /dev/null @@ -1,25 +0,0 @@ -// @ts-nocheck -const { addConfigOptions, addAccountOptions } = require('../lib/commonOpts'); - -const addSecretCommand = require('./secrets/addSecret'); -const listSecretsCommand = require('./secrets/listSecrets'); -const deleteSecretCommand = require('./secrets/deleteSecret'); -const updateSecretCommand = require('./secrets/updateSecret'); -const { i18n } = require('../lib/lang'); - -const i18nKey = 'commands.secrets'; - -exports.command = 'secrets'; -exports.describe = i18n(`${i18nKey}.describe`); - -exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - yargs - .command(listSecretsCommand) - .command(addSecretCommand) - .command(updateSecretCommand) - .command(deleteSecretCommand) - .demandCommand(1, ''); - return yargs; -}; diff --git a/commands/secrets/deleteSecret.ts b/commands/secrets/deleteSecret.ts deleted file mode 100644 index 695db6302..000000000 --- a/commands/secrets/deleteSecret.ts +++ /dev/null @@ -1,64 +0,0 @@ -// @ts-nocheck -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { ApiErrorContext, logError } = require('../../lib/errorHandlers/index'); -const { deleteSecret } = require('@hubspot/local-dev-lib/api/secrets'); - -const { loadAndValidateOptions } = require('../../lib/validation'); -const { trackCommandUsage } = require('../../lib/usageTracking'); -const { uiAccountDescription } = require('../../lib/ui'); - -const { - addConfigOptions, - addAccountOptions, - addUseEnvironmentOptions, - getAccountId, -} = require('../../lib/commonOpts'); -const { i18n } = require('../../lib/lang'); - -const i18nKey = 'commands.secrets.subcommands.delete'; - -exports.command = 'delete '; -exports.describe = i18n(`${i18nKey}.describe`); - -exports.handler = async options => { - const { name: secretName } = options; - - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - trackCommandUsage('secrets-delete', null, accountId); - - try { - await deleteSecret(accountId, secretName); - logger.success( - i18n(`${i18nKey}.success.delete`, { - accountIdentifier: uiAccountDescription(accountId), - secretName, - }) - ); - } catch (err) { - logger.error( - i18n(`${i18nKey}.errors.delete`, { - secretName, - }) - ); - logError( - err, - new ApiErrorContext({ - request: 'delete a secret', - accountId, - }) - ); - } -}; - -exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addUseEnvironmentOptions(yargs); - yargs.positional('name', { - describe: i18n(`${i18nKey}.positionals.name.describe`), - type: 'string', - }); - return yargs; -}; diff --git a/commands/theme.ts b/commands/theme.ts index 1c176d564..e80b5ceee 100644 --- a/commands/theme.ts +++ b/commands/theme.ts @@ -2,15 +2,18 @@ const marketplaceValidate = require('./theme/marketplace-validate'); const generateSelectors = require('./theme/generate-selectors'); const previewCommand = require('./theme/preview'); +const { addGlobalOptions } = require('../lib/commonOpts'); const { i18n } = require('../lib/lang'); const i18nKey = 'commands.theme'; -exports.command = 'theme'; +exports.command = ['theme', 'themes']; exports.describe = i18n(`${i18nKey}.describe`); exports.builder = yargs => { + addGlobalOptions(yargs); + yargs .command(previewCommand) .command(marketplaceValidate) diff --git a/commands/theme/generate-selectors.ts b/commands/theme/generate-selectors.ts index 72d28853a..3bb84da75 100644 --- a/commands/theme/generate-selectors.ts +++ b/commands/theme/generate-selectors.ts @@ -25,20 +25,20 @@ const THEME_PATH_REGEX = new RegExp(/=\s*.*(theme\.(\w|\.)*)/, 'i'); const i18nKey = 'commands.theme.subcommands.generateSelectors'; -exports.command = 'generate-selectors '; +exports.command = 'generate-selectors '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = options => { - const { themePath } = options; + const { path } = options; - const fieldsJsonPath = findFieldsJsonPath(themePath); + const fieldsJsonPath = findFieldsJsonPath(path); if (!fieldsJsonPath) { logger.error(i18n(`${i18nKey}.errors.fieldsNotFound`)); process.exit(EXIT_CODES.ERROR); } let fieldsJson = JSON.parse(fs.readFileSync(fieldsJsonPath)); - let cssString = combineThemeCss(themePath); + let cssString = combineThemeCss(path); /** * Creates map of HubL variable names to theme field paths @@ -190,7 +190,7 @@ exports.handler = options => { } const selectorsMap = generateSelectorsMap(fieldsJson); - const selectorsPath = `${themePath}/editor-preview.json`; + const selectorsPath = `${path}/editor-preview.json`; fs.writeFileSync( selectorsPath, @@ -199,15 +199,15 @@ exports.handler = options => { logger.success( i18n(`${i18nKey}.success`, { - themePath, + themePath: path, selectorsPath, }) ); }; exports.builder = yargs => { - yargs.positional('themePath', { - describe: i18n(`${i18nKey}.positionals.themePath.describe`), + yargs.positional('path', { + describe: i18n(`${i18nKey}.positionals.path.describe`), type: 'string', }); diff --git a/commands/theme/marketplace-validate.ts b/commands/theme/marketplace-validate.ts index a4e59b23c..8c12949f1 100644 --- a/commands/theme/marketplace-validate.ts +++ b/commands/theme/marketplace-validate.ts @@ -4,9 +4,7 @@ const { addConfigOptions, addAccountOptions, addUseEnvironmentOptions, - getAccountId, } = require('../../lib/commonOpts'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { trackCommandUsage } = require('../../lib/usageTracking'); const { kickOffValidation, @@ -19,34 +17,34 @@ const { i18n } = require('../../lib/lang'); const i18nKey = 'commands.theme.subcommands.marketplaceValidate'; -exports.command = 'marketplace-validate '; +exports.command = 'marketplace-validate '; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { src } = options; + const { path, derivedAccountId } = options; - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); - - trackCommandUsage('validate', null, accountId); + trackCommandUsage('validate', null, derivedAccountId); SpinniesManager.init(); SpinniesManager.add('marketplaceValidation', { text: i18n(`${i18nKey}.logs.validatingTheme`, { - path: src, + path, }), }); const assetType = 'THEME'; - const validationId = await kickOffValidation(accountId, assetType, src); - await pollForValidationFinish(accountId, validationId); + const validationId = await kickOffValidation( + derivedAccountId, + assetType, + path + ); + await pollForValidationFinish(derivedAccountId, validationId); SpinniesManager.remove('marketplaceValidation'); const validationResults = await fetchValidationResults( - accountId, + derivedAccountId, validationId ); processValidationErrors(i18nKey, validationResults); @@ -60,8 +58,8 @@ exports.builder = yargs => { addAccountOptions(yargs); addUseEnvironmentOptions(yargs); - yargs.positional('src', { - describe: i18n(`${i18nKey}.positionals.src.describe`), + yargs.positional('path', { + describe: i18n(`${i18nKey}.positionals.path.describe`), type: 'string', }); return yargs; diff --git a/commands/theme/preview.ts b/commands/theme/preview.ts index d5cfc7323..c82f6dd46 100644 --- a/commands/theme/preview.ts +++ b/commands/theme/preview.ts @@ -3,15 +3,10 @@ const fs = require('fs'); const path = require('path'); const { i18n } = require('../../lib/lang'); const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - addAccountOptions, - addConfigOptions, - getAccountId, -} = require('../../lib/commonOpts'); +const { addAccountOptions, addConfigOptions } = require('../../lib/commonOpts'); const { getCwd } = require('@hubspot/local-dev-lib/path'); const { getUploadableFileList } = require('../../lib/upload'); const { trackCommandUsage } = require('../../lib/usageTracking'); -const { loadAndValidateOptions } = require('../../lib/validation'); const { previewPrompt, previewProjectPrompt, @@ -28,7 +23,7 @@ const { getProjectConfig } = require('../../lib/projects'); const { findProjectComponents, COMPONENT_TYPES, -} = require('../../lib/projectStructure'); +} = require('../../lib/projects/structure'); const { preview } = require('@hubspot/theme-preview-dev-server'); const { hasFeature } = require('../../lib/hasFeature'); const i18nKey = 'commands.theme.subcommands.preview'; @@ -108,11 +103,14 @@ const determineSrcAndDest = async options => { }; exports.handler = async options => { - const { notify, noSsl, resetSession, port, generateFieldsTypes } = options; - - await loadAndValidateOptions(options); - - const accountId = getAccountId(options); + const { + derivedAccountId, + notify, + noSsl, + resetSession, + port, + generateFieldsTypes, + } = options; const { absoluteSrc, dest } = await determineSrcAndDest(options); @@ -166,7 +164,7 @@ exports.handler = async options => { logError( result.error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: dest, payload: result.file, }) @@ -178,7 +176,7 @@ exports.handler = async options => { return uploadOptions; }; - trackCommandUsage('preview', accountId); + trackCommandUsage('preview', derivedAccountId); let createUnifiedDevServer; try { @@ -192,7 +190,7 @@ exports.handler = async options => { } const isUngatedForUnified = await hasFeature( - accountId, + derivedAccountId, 'cms:react:unifiedThemePreview' ); if (isUngatedForUnified && createUnifiedDevServer) { @@ -215,7 +213,7 @@ exports.handler = async options => { } ); } else { - preview(accountId, absoluteSrc, dest, { + preview(derivedAccountId, absoluteSrc, dest, { notify, filePaths, noSsl, diff --git a/commands/upload.ts b/commands/upload.ts index 869e4a889..feb37b63c 100644 --- a/commands/upload.ts +++ b/commands/upload.ts @@ -28,14 +28,14 @@ const { const { addConfigOptions, addAccountOptions, - addModeOptions, + addCmsPublishModeOptions, addUseEnvironmentOptions, - getAccountId, - getMode, + addGlobalOptions, + getCmsPublishMode, } = require('../lib/commonOpts'); const { uploadPrompt } = require('../lib/prompts/uploadPrompt'); -const { cleanUploadPrompt } = require('../lib/prompts/cleanUploadPrompt'); -const { validateMode, loadAndValidateOptions } = require('../lib/validation'); +const { confirmPrompt } = require('../lib/prompts/promptUtils'); +const { validateCmsPublishMode } = require('../lib/validation'); const { trackCommandUsage } = require('../lib/usageTracking'); const { getUploadableFileList } = require('../lib/upload'); @@ -48,7 +48,7 @@ const { cleanupTmpDirSync, } = require('@hubspot/local-dev-lib/cms/handleFieldsJS'); -exports.command = 'upload [--src] [--dest]'; +exports.command = 'upload [src] [dest]'; exports.describe = i18n(`${i18nKey}.describe`); const logThemePreview = (filePath, accountId) => { @@ -64,14 +64,12 @@ const logThemePreview = (filePath, accountId) => { }; exports.handler = async options => { - await loadAndValidateOptions(options); - - if (!validateMode(options)) { + if (!validateCmsPublishMode(options)) { process.exit(EXIT_CODES.WARNING); } - const accountId = getAccountId(options); - const mode = getMode(options); + const { derivedAccountId } = options; + const cmsPublishMode = getCmsPublishMode(options); const uploadPromptAnswers = await uploadPrompt(options); const src = options.src || uploadPromptAnswers.src; @@ -126,8 +124,8 @@ exports.handler = async options => { const normalizedDest = convertToUnixPath(dest); trackCommandUsage( 'upload', - { mode, type: stats.isFile() ? 'file' : 'folder' }, - accountId + { mode: cmsPublishMode, type: stats.isFile() ? 'file' : 'folder' }, + derivedAccountId ); const srcDestIssues = await validateSrcAndDestPaths( { isLocal: true, path: src }, @@ -157,20 +155,20 @@ exports.handler = async options => { return; } upload( - accountId, + derivedAccountId, absoluteSrcPath, normalizedDest, - getFileMapperQueryValues(mode, options) + getFileMapperQueryValues(cmsPublishMode, options) ) .then(() => { logger.success( i18n(`${i18nKey}.success.fileUploaded`, { - accountId, + accountId: derivedAccountId, dest: normalizedDest, src, }) ); - logThemePreview(src, accountId); + logThemePreview(src, derivedAccountId); }) .catch(error => { logger.error( @@ -182,7 +180,7 @@ exports.handler = async options => { logError( error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: normalizedDest, payload: src, }) @@ -199,7 +197,7 @@ exports.handler = async options => { } else { logger.log( i18n(`${i18nKey}.uploading`, { - accountId, + accountId: derivedAccountId, dest, src, }) @@ -215,18 +213,27 @@ exports.handler = async options => { // If clean is true, will first delete the dest folder and then upload src. Cleans up files that only exist on HS. let cleanUpload = options.force; if (!options.force) { - cleanUpload = await cleanUploadPrompt(accountId, dest); + cleanUpload = await confirmPrompt( + i18n(`${i18nKey}.confirmCleanUpload`, { + accountId: derivedAccountId, + path: dest, + }), + { defaultAnswer: false } + ); } if (cleanUpload) { try { - await deleteFile(accountId, dest); + await deleteFile(derivedAccountId, dest); logger.log( - i18n(`${i18nKey}.cleaning`, { accountId, filePath: dest }) + i18n(`${i18nKey}.cleaning`, { + accountId: derivedAccountId, + filePath: dest, + }) ); } catch (error) { logger.error( i18n(`${i18nKey}.errors.deleteFailed`, { - accountId, + accountId: derivedAccountId, path: dest, }) ); @@ -234,11 +241,11 @@ exports.handler = async options => { } } uploadFolder( - accountId, + derivedAccountId, absoluteSrcPath, dest, { - mode, + cmsPublishMode, }, options, filePaths @@ -250,7 +257,7 @@ exports.handler = async options => { dest, }) ); - logThemePreview(src, accountId); + logThemePreview(src, derivedAccountId); } else { logger.error( i18n(`${i18nKey}.errors.someFilesFailed`, { @@ -268,7 +275,7 @@ exports.handler = async options => { }) ); logError(error, { - accountId, + accountId: derivedAccountId, }); process.exit(EXIT_CODES.WARNING); }); @@ -276,11 +283,6 @@ exports.handler = async options => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addModeOptions(yargs, { write: true }); - addUseEnvironmentOptions(yargs); - yargs.positional('src', { describe: i18n(`${i18nKey}.positionals.src.describe`), type: 'string', @@ -315,5 +317,12 @@ exports.builder = yargs => { type: 'boolean', default: false, }); + + addConfigOptions(yargs); + addAccountOptions(yargs); + addCmsPublishModeOptions(yargs, { write: true }); + addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); + return yargs; }; diff --git a/commands/watch.ts b/commands/watch.ts index 3a782a080..ef75b0fea 100644 --- a/commands/watch.ts +++ b/commands/watch.ts @@ -9,13 +9,13 @@ const { logger } = require('@hubspot/local-dev-lib/logger'); const { addConfigOptions, addAccountOptions, - addModeOptions, + addCmsPublishModeOptions, addUseEnvironmentOptions, - getAccountId, - getMode, + addGlobalOptions, + getCmsPublishMode, } = require('../lib/commonOpts'); const { uploadPrompt } = require('../lib/prompts/uploadPrompt'); -const { validateMode, loadAndValidateOptions } = require('../lib/validation'); +const { validateCmsPublishMode } = require('../lib/validation'); const { trackCommandUsage } = require('../lib/usageTracking'); const { i18n } = require('../lib/lang'); const { getUploadableFileList } = require('../lib/upload'); @@ -24,20 +24,18 @@ const i18nKey = 'commands.watch'; const { EXIT_CODES } = require('../lib/enums/exitCodes'); -exports.command = 'watch [--src] [--dest]'; +exports.command = 'watch [src] [dest]'; exports.describe = i18n(`${i18nKey}.describe`); exports.handler = async options => { - const { remove, initialUpload, disableInitial, notify } = options; + const { remove, initialUpload, disableInitial, notify, derivedAccountId } = + options; - await loadAndValidateOptions(options); - - if (!validateMode(options)) { + if (!validateCmsPublishMode(options)) { process.exit(EXIT_CODES.ERROR); } - const accountId = getAccountId(options); - const mode = getMode(options); + const cmsPublishMode = getCmsPublishMode(options); const uploadPromptAnswers = await uploadPrompt(options); @@ -85,7 +83,7 @@ exports.handler = async options => { ); } - trackCommandUsage('watch', { mode }, accountId); + trackCommandUsage('watch', { mode: cmsPublishMode }, derivedAccountId); const postInitialUploadCallback = null; const onUploadFolderError = error => { @@ -93,37 +91,37 @@ exports.handler = async options => { i18n(`${i18nKey}.errors.folderFailed`, { src, dest, - accountId, + accountId: derivedAccountId, }) ); logError(error, { - accountId, + accountId: derivedAccountId, }); }; const onQueueAddError = null; - const onUploadFileError = (file, dest, accountId) => error => { + const onUploadFileError = (file, dest, derivedAccountId) => error => { logger.error( i18n(`${i18nKey}.errors.fileFailed`, { file, dest, - accountId, + accountId: derivedAccountId, }) ); logError( error, new ApiErrorContext({ - accountId, + accountId: derivedAccountId, request: dest, payload: file, }) ); }; watch( - accountId, + derivedAccountId, absoluteSrcPath, dest, { - mode, + cmsPublishMode, remove, disableInitial: initialUpload ? false : true, notify, @@ -138,11 +136,6 @@ exports.handler = async options => { }; exports.builder = yargs => { - addConfigOptions(yargs); - addAccountOptions(yargs); - addModeOptions(yargs, { write: true }); - addUseEnvironmentOptions(yargs); - yargs.positional('src', { describe: i18n(`${i18nKey}.positionals.src.describe`), type: 'string', @@ -188,5 +181,12 @@ exports.builder = yargs => { type: 'boolean', default: false, }); + + addConfigOptions(yargs); + addAccountOptions(yargs); + addCmsPublishModeOptions(yargs, { write: true }); + addUseEnvironmentOptions(yargs); + addGlobalOptions(yargs); + return yargs; }; diff --git a/docs/HubspotConfigFile.md b/docs/HubspotConfigFile.md index 002c68a6b..2a106310e 100644 --- a/docs/HubspotConfigFile.md +++ b/docs/HubspotConfigFile.md @@ -39,7 +39,7 @@ description: Specifies the default portal(account) to use with the `hs` commands default: Whatever the first accountId added to the config is -### defaultMode +### defaultCmsPublishMode type: `draft` or `publish` or _Undefined_ diff --git a/lang/en.lyaml b/lang/en.lyaml index 9e19a139b..ec2cb9694 100644 --- a/lang/en.lyaml +++ b/lang/en.lyaml @@ -7,8 +7,14 @@ en: cliUpdateNotification: "HubSpot CLI version {{#cyan}}{{#bold}}{currentVersion}{{/bold}}{{/cyan}} is outdated.\nRun {{ updateCommand }} to upgrade to version {{#cyan}}{{#bold}}{latestVersion}{{/bold}}{{/cyan}}" srcIsProject: "\"{{ src }}\" is in a project folder. Did you mean \"hs project {{command}}\"?" setDefaultAccountMoved: "This command has moved. Try `hs accounts use` instead" - accounts: - describe: "Commands for working with accounts." + loadConfigMiddleware: + configFileExists: "A configuration file already exists at {{ configPath }}. To specify a new configuration file, delete the existing one and try again." + completion: + describe: "Enable bash completion shortcuts for commands. Concat the generated script to your .bashrc, .bash_profile, or .zshrc file." + examples: + default: "Generate shell completion scripts for the zsh shell" + account: + describe: "Commands for managing configured accounts." subcommands: list: accounts: "{{#bold}}Accounts{{/bold}}:" @@ -20,7 +26,7 @@ en: authType: "Auth Type" name: "Name" rename: - describe: "Rename account in config." + describe: "Rename an account in the config." positionals: accountName: describe: "Name of account to be renamed." @@ -61,7 +67,7 @@ en: accountRemoved: "Account \"{{ accountName }}\" removed from the config" info: accountId: "{{#bold}}Account ID{{/bold}}: {{ accountId }}" - describe: "Print information about the default account, or about the account specified with the \"--account\" option." + describe: "Print information about the default account, or about the account specified with the \"account\" option." errors: notUsingPersonalAccessKey: "This command currently only supports fetching scopes for the personal access key auth type." examples: @@ -71,7 +77,7 @@ en: name: "{{#bold}}Account name{{/bold}}: {{ name }}" scopeGroups: "{{#bold}}Scopes available{{/bold}}:" clean: - describe: "Checks for inactive accounts and removes them from the CLI config" + describe: "Check for inactive accounts and removes them from the CLI config." noResults: "No inactive accounts found to remove." loading: add: "Looking for inactive accounts…" @@ -87,28 +93,27 @@ en: errors: noConfigFileFound: "No config file was found. To create a new config file, use the \"hs init\" command." unsupportedAuthType: "Unsupported auth type: {{ type }}. The only supported authentication protocols are {{ supportedProtocols }}." - positionals: - type: - describe: "Authentication mechanism" - defaultDescription: "\"{{ authMethod }}\": \nAn access token tied to a specific user account. This is the recommended way of authenticating with local development tools." options: + authType: + describe: "Authentication mechanism" + defaultDescription: "\"{{ authMethod }}\": An access token tied to a specific user account. This is the recommended way of authenticating with local development tools." account: describe: "HubSpot account to authenticate" success: configFileUpdated: "Account \"{{ accountName }}\" updated in {{ configFilename }} using \"{{ authType }}\"" config: - describe: "Commands for working with the config file." + describe: "Commands for managing the CLI config file." subcommands: set: - describe: "Commands for modifying the CLI configuration" + describe: "Set various configuration options within the hubspot.config.yml file." promptMessage: "Select a config option to update" examples: default: "Opens a prompt to select a config item to modify" options: defaultMode: - describe: "Set the default mode" - promptMessage: "Select mode to be used as the default" - error: "The mode \"{{ mode }}\" is invalid. Valid values are {{ validModes }}." + describe: "Set the default CMS publish mode" + promptMessage: "Select CMS publish mode to be used as the default" + error: "The provided CMS publish mode is invalid. Valid values are {{ validModes }}." success: "Default mode updated to: {{ mode }}" allowUsageTracking: describe: "Enable or disable usage tracking" @@ -150,9 +155,10 @@ en: describe: "Medium to test against" verbose: describe: "View a detailed output of the lighthouse sores" - reactModule: - describe: "Get a specified default React module" - options: + getReactModule: + describe: "Get a specified default React module." + selectModulePrompt: "Select a React module to download" + positionals: name: describe: "Name of the react modules to be fetched" dest: @@ -162,7 +168,6 @@ en: errors: pathExists: "Folder already exists at \"{{ path }}\"" invalidName: "Module not found with that name, please check the spelling of the module you are trying to download." - groupLabel: "React modules available to download:" create: describe: "Create HubSpot sample apps and CMS assets. Supported assets are {{ supportedAssetTypes }}." errors: @@ -194,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: @@ -230,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: @@ -244,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: @@ -256,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: @@ -298,10 +317,10 @@ en: src: describe: "Path in HubSpot Design Tools" filemanager: - describe: "Commands for working with the File Manager." + describe: "Commands for managing files in the File Manager." subcommands: fetch: - describe: "Download a folder or file from the HubSpot File Manager to your computer" + describe: "Fetch a folder or file from the File Manager." errors: sourceRequired: "A source to fetch is required." options: @@ -313,7 +332,7 @@ en: src: describe: "Path to the local directory you would like the files to be placed, relative to your current working directory. If omitted, this argument will default to your current working directory" upload: - describe: "Upload a folder or file from your computer to the HubSpot File Manager" + describe: "Upload a folder or file to the File Manager." errors: destinationRequired: "A destination path needs to be passed" fileIgnored: "The file \"{{ path }}\" is being ignored via an .hsignore rule" @@ -330,8 +349,8 @@ en: success: upload: "Uploaded file from \"{{ src }}\" to \"{{ dest }}\" in the File Manager of account {{ accountId }}" uploadComplete: "Uploading files to \"{{ dest }}\" in the File Manager is complete" - functions: - describe: "Commands for working with functions." + function: + describe: "Commands for managing CMS serverless functions." subcommands: deploy: debug: @@ -346,13 +365,13 @@ en: loadingFailed: "Failed to build and deploy bundle for \"{{ functionPath }}\" on {{ account }}" positionals: path: - describe: "Path to .functions folder" + describe: "Path to the \".functions\" folder" success: deployed: "Built and deployed bundle from package.json for {{ functionPath }} on account {{ accountId }} in {{ buildTimeSeconds }}s." list: debug: gettingFunctions: "Getting currently deployed functions" - describe: "List currently deployed functions" + describe: "List the currently deployed CMS serverless functions." info: noFunctions: "No functions found" options: @@ -376,10 +395,10 @@ en: path: describe: "Path to local .functions folder" hubdb: - describe: "Manage HubDB tables." + describe: "Commands for managing HubDB tables." subcommands: clear: - describe: "Clear all rows in a HubDB table" + describe: "Clear all rows in a HubDB table." logs: removedRows: "Removed {{ deletedRowCount }} rows from HubDB table {{ tableId }}" rowCount: "HubDB table {{ tableId }} now contains {{ rowCount }} rows" @@ -388,25 +407,32 @@ en: tableId: describe: "HubDB Table ID" create: - describe: "Create a HubDB table" + describe: "Create a HubDB table." + enterPath: "[--path] Enter the local path to the file used for import:" errors: - create: "Creating the table at \"{{ src }}\" failed" - positionals: - src: + create: "Creating the table at \"{{ filePath }}\" failed" + pathRequired: "A path to a local file with a HubDB schema is required to create a HubDB table" + invalidCharacters: "The selected file path contains invalid characters. Please provide a new path and try again." + options: + path: describe: "Local path to file used for import" success: create: "The table {{ tableId }} was created in {{ accountId }} with {{ rowCount }} rows" delete: - describe: "Delete a HubDB table" + describe: "Delete a HubDB table." + shouldDeleteTable: "Proceed with deleting HubDB table {{ tableId }}?" errors: delete: "Deleting the table {{ tableId }} failed" positionals: tableId: describe: "HubDB Table ID" + options: + force: + describe: "Skips confirmation prompt when deleting a HubDB table" success: delete: "The table {{ tableId }} was deleted from {{ accountId }}" fetch: - describe: "Fetch a HubDB table" + describe: "Fetch the schema for a HubDB table." positionals: dest: describe: "Local destination folder to fetch table to" @@ -417,11 +443,13 @@ en: init: describe: "Initialize {{ configName }} for a HubSpot account." options: - auth: - describe: "Specify auth method to use [\"personalaccesskey\", \"oauth2\"]" - defaultDescription: "\"{{ defaultType }}\": \nAn access token tied to a specific user account. This is the recommended way of authenticating with local development tools." + authType: + describe: "Authentication mechanism" + defaultDescription: "\"{{ authMethod }}\": An access token tied to a specific user account. This is the recommended way of authenticating with local development tools." account: describe: "HubSpot account to authenticate" + useHiddenConfig: + describe: "Use the new HubSpot configuration file located in a hidden file in the user's home directory" success: configFileCreated: "Created config file \"{{ configPath }}\"" configFileUpdated: "Connected account \"{{ account }}\" using \"{{ authType }}\" and set it as the default account" @@ -429,6 +457,7 @@ en: updateConfig: "To update an existing config file, use the \"hs auth\" command." errors: configFileExists: "The config file {{ configPath }} already exists." + bothConfigFilesNotAllowed: "Unable to create config file, because there is an existing one at \"{{ path }}\". To create a new config file, delete the existing one and try again." lint: issuesFound: "{{ count }} issues found." groupName: "Linting {{ path }}" @@ -443,13 +472,14 @@ en: path: describe: "Remote directory to list contents" logs: - describe: "Get logs for a function." + describe: "View logs for a CMS serverless function." errors: noLogsFound: "No logs were found for the function path \"{{ functionPath }}\" in account \"{{ accountId }}\"." examples: default: "Get 5 most recent logs for function residing at /_hcms/api/my-endpoint" follow: "Poll for and output logs for function residing at /_hcms/api/my-endpoint immediately upon new execution" limit: "Get 10 most recent logs for function residing at /_hcms/api/my-endpoint" + endpointPrompt: "Enter a serverless function endpoint:" gettingLogs: "Getting {{#if latest}}latest {{/if}}logs for function with path: {{ functionPath }}." options: compact: @@ -471,7 +501,7 @@ en: moveFailed: "Moving \"{{ srcPath }}\" to \"{{ destPath }}\" in account {{ accountId }} failed" move: "Moved \"{{ srcPath }}\" to \"{{ destPath }}\" in account {{ accountId }}" open: - describe: "Open a HubSpot page in your browser. Run \"hs open list\" to see all available shortcuts." + describe: "Open a HubSpot page in your browser." options: list: describe: "List all supported shortcuts" @@ -480,10 +510,10 @@ en: describe: "Shortcut of the link you'd like to open" selectLink: "Select a link to open" project: - describe: "Commands for working with projects. For more information, visit our documentation: https://developers.hubspot.com/docs/platform/build-and-deploy-using-hubspot-projects" + describe: "Commands for managing projects. For more information, visit our documentation: https://developers.hubspot.com/docs/platform/build-and-deploy-using-hubspot-projects" subcommands: dev: - describe: "Start local dev for the current project" + describe: "Start local dev for the current project." logs: betaMessage: "HubSpot projects local development" placeholderAccountSelection: "Using default account as target account (for now)" @@ -495,14 +525,14 @@ en: examples: default: "Start local dev for the current project" create: - describe: "Create a new project" + describe: "Create a new project." logs: welcomeMessage: "Welcome to HubSpot Developer Projects!" examples: default: "Create a new project" options: - location: - describe: "Directory where project should be created" + dest: + describe: "Directory where the project should be created" name: describe: "Project name (cannot be changed)" template: @@ -510,14 +540,14 @@ en: templateSource: describe: "Path to custom GitHub repository from which to create project template" migrateApp: - describe: "Migrate a public app to the projects framework" + describe: "Migrate a public app to the projects framework." examples: default: "Migrate a public app to the projects framework" options: appId: describe: "The ID for the public app being migrated to the projects framework" - location: - describe: "Directory where project should be created" + dest: + describe: "Directory where the project should be created" name: describe: "Project name (cannot be changed)" header: @@ -544,25 +574,25 @@ en: projectAlreadyExists: "A project with name {{ projectName }} already exists. Please choose another name." invalidApp: "Could not migrate appId {{ appId }}. This app cannot be migrated at this time. Please choose another public app." cloneApp: - describe: "Clone a public app using the projects framework" + describe: "Clone a public app using the projects framework." examples: default: "Clone a public app using the projects framework" options: appId: describe: "The ID for the public app being cloned" - location: - describe: "Directory where project should be created" + dest: + describe: "Directory where the project should be created" cloneStatus: inProgress: "Cloning app configuration to {{#bold}}public-app.json{{/bold}} component definition ..." done: "Cloning app configuration to public-app.json component definition ... DONE" - success: "Your cloned project was created in {{ location }}" + success: "Your cloned project was created in {{ dest }}" failure: "Cloning app configuration to public-app.json component definition ... FAILED" errors: invalidAccountTypeTitle: "{{#bold}}Developer account not targeted{{/bold}}" invalidAccountTypeDescription: "Only public apps created in a developer account can be converted to a project component. Select a connected developer account with {{useCommand}} or {{authCommand}} and try again." couldNotWriteConfigPath: "Failed to write project config at {{ configPath }}" add: - describe: "Create a new component within a project" + describe: "Create a new component within a project." options: name: describe: "The name for your newly created component" @@ -578,7 +608,7 @@ en: default: "Create a component within your project" withFlags: "Use --name and --type flags to bypass the prompt." deploy: - describe: "Deploy a project build" + describe: "Deploy a project build." deployBuildIdPrompt: "[--build] Deploy which build?" debug: deploying: "Deploying project at path: {{ path }}" @@ -599,9 +629,23 @@ en: project: describe: "Project name" listBuilds: - describe: "List the project's builds" + describe: "List the project's builds." + continueOrExitPrompt: "Press to load more, or ctrl+c to exit" + viewAllBuildsLink: "View all builds" + showingNextBuilds: "Showing the next {{ count }} builds for {{ projectName }}" + showingRecentBuilds: "Showing the most {{ count }} recent builds for {{ projectName }}. {{ viewAllBuildsLink }}." + errors: + noBuilds: "No builds for this project were found." + projectNotFound: "Project {{ projectName}} not found." + options: + project: + describe: "Project name" + limit: + describe: "Limit the number of builds to output" + examples: + default: "List the builds for the current project" logs: - describe: "Get execution logs for a serverless function within a project" + describe: "Get execution logs for a serverless function within a project." errors: noProjectConfig: "No project detected. Run this command again from a project directory." failedToFetchProjectDetails: "There was an error fetching project details" @@ -609,6 +653,8 @@ en: noFunctionsInProject: "There aren't any functions in this project\n\t- Run `{{#orange}}hs project logs --help{{/orange}}` to learn more about logs\n\t- {{link}} to learn more about serverless functions" noFunctionWithName: "No function with name \"{{ name }}\"" functionNotDeployed: "The function with name \"{{ name }}\" is not deployed" + projectLogsManagerNotInitialized: "Function called on ProjectLogsManager before initialization" + generic: "Error fetching logs" logs: showingLogs: "Showing logs for:" hubspotLogsDirectLink: "View function logs in HubSpot" @@ -634,9 +680,9 @@ en: function: describe: "App function name" upload: - describe: "Upload your project files and create a new build" + describe: "Upload your project files and create a new build." examples: - default: "Upload a project" + default: "Upload a project into your HubSpot account" logs: buildSucceeded: "Build #{{ buildId }} succeeded\n" readyToGoLive: "🚀 Ready to take your project live?" @@ -649,13 +695,10 @@ en: describe: "Automatically create project if it does not exist" message: describe: "Add a message when you upload your project and create a build" - positionals: - path: - describe: "Path to a project folder" watch: - describe: "Watch your local project for changes and automatically upload changed files to a new build in HubSpot" + describe: "Watch your local project for changes and automatically upload changed files to a new build in HubSpot." examples: - default: "Watch a project within the myProjectFolder folder" + default: "Start watching the current project" logs: processExited: "Stopping watcher..." watchCancelledFromUi: "The watch process has been cancelled from the UI. Any changes made since cancelling have not been uploaded. To resume watching, rerun {{#yellow}}`hs project watch`{{/yellow}}." @@ -665,9 +708,6 @@ en: deleteFolderSucceeded: "Deleted folder \"{{ remotePath }}\"" watching: "Watcher is ready and watching \"{{ projectDir }}\". Any changes detected will be automatically uploaded." previousStagingBuildCancelled: "Killed the previous watch process. Please try running `hs project watch` again" - positionals: - path: - describe: "Path to a project folder" options: initialUpload: describe: "Upload directory before watching for updates" @@ -684,7 +724,7 @@ en: deleteFileFailed: "Failed to delete file \"{{ remotePath }}\"" deleteFolderFailed: "Failed to delete folder \"{{ remotePath }}\"" download: - describe: "Download your project files from HubSpot and write to a path on your computer" + describe: "Download your project files from HubSpot." examples: default: "Download the project myProject into myProjectFolder folder" logs: @@ -696,14 +736,14 @@ en: warnings: cannotDownloadWithinProject: "Cancelling project download. Please run the command again outside the context of an existing project." options: - buildNumber: - describe: "The build number to download" + build: + describe: "The build to download" project: describe: "The name of the project to download" dest: describe: "Destination folder for the project" open: - describe: "Open the specified project's details page in the browser" + describe: "Open the project's details page in the browser." options: project: describe: "Name of project to open" @@ -711,7 +751,7 @@ en: default: "Opens the projects page for the specified account" success: "Successfully opened \"{{ projectName }}\"" feedback: - describe: "Leave feedback on HubSpot projects or file a bug report" + describe: "Leave feedback on HubSpot projects or file a bug report." feedbackType: prompt: "What type of feedback would you like to leave?" bug: "[--bug] Report a bug" @@ -725,7 +765,7 @@ en: describe: "Open Github issues in your browser to give feedback." installDeps: help: - describe: "Install the dependencies for your project, or add a dependency to a subcomponent of a project" + describe: "Install the dependencies for your project, or add a dependency to a subcomponent of a project." installAppDepsExample: "Install the dependencies for the project" addDepToSubComponentExample: "Install the dependencies to one or more project subcomponents" installLocationPrompt: "Choose the project components to install the dependencies:" @@ -746,18 +786,19 @@ en: path: describe: "Remote hubspot path" sandbox: - describe: "Commands for working with sandboxes." + describe: "Commands for managing sandboxes." subcommands: create: - describe: "Create a sandbox account" + describe: "Create a sandbox account." examples: default: "Creates a standard sandbox account named MySandboxAccount." - force: "Skips all confirmation prompts when creating a sandbox account." debug: error: "Error creating sandbox:" info: auth: "Run `hs auth` to authenticate with the new sandbox account." options: + force: + describe: "Skips all confirmation prompts when creating a sandbox account." name: describe: "Name to use for created sandbox" type: @@ -770,13 +811,12 @@ en: \n- Run {{#bold}}hs accounts use{{/bold}} to switch to your default account to your production account. \n- Run {{#bold}}hs auth{{/bold}} to connect a production account to the HubSpot CLI.\n" delete: - describe: "Delete a sandbox account" + describe: "Delete a sandbox account." debug: deleting: "Deleting sandbox account \"{{ account }}\"" error: "Error deleting sandbox account:" examples: default: "Deletes the sandbox account named MySandboxAccount." - force: "Skips all confirmation prompts when deleting a sandbox account." confirm: "Delete sandbox {{#bold}}{{ account }}{{/bold}}? All data for this sandbox will be permanently deleted." defaultAccountWarning: "The sandbox {{#bold}}{{ account }}{{/bold}} is currently set as the default account." success: @@ -792,38 +832,47 @@ en: noParentPortalAvailable: "This sandbox can't be deleted from the CLI because you haven't given the CLI access to its parent account. To do this, run {{#bold}}{{ command }}{{/bold}}. You can also delete the sandbox from the HubSpot management tool: {{#bold}}{{ url }}{{/bold}}." invalidKey: "Your personal access key for account {{#bold}}{{ account }}{{/bold}} is inactive. To re-authenticate, please run {{#bold}}hs auth personalaccesskey{{/bold}}." options: + force: + describe: "Skips all confirmation prompts when deleting a sandbox account." account: describe: "Account name or id to delete" - secrets: - describe: "Manage HubSpot secrets." + secret: + describe: "Commands for managing secrets." subcommands: add: - describe: "Add a HubSpot secret" + describe: "Create a new secret." errors: add: "The secret \"{{ secretName }}\" was not added" + alreadyExists: "The secret \"{{ secretName }}\" already exists, it's value can be modified with {{ command }}" positionals: name: describe: "Name of the secret" success: add: "The secret \"{{ secretName }}\" was added to the HubSpot account: {{ accountIdentifier }}" delete: - describe: "Delete a HubSpot secret" + describe: "Delete a secret." + selectSecret: "Select the secret you want to delete" + deleteCanceled: "Delete canceled" + confirmDelete: "Are you sure you want to delete the secret \"{{ secretName }}\"?" errors: delete: "The secret \"{{ secretName }}\" was not deleted" + noSecret: "Unable to delete secret with name \"{{ secretName }}\", it does not exist" positionals: name: describe: "Name of the secret" success: delete: "The secret \"{{ secretName }}\" was deleted from the HubSpot account: {{ accountIdentifier }}" list: - describe: "List all HubSpot secrets" + describe: "List all secrets." errors: list: "The secrets could not be listed" groupLabel: "Secrets for account {{ accountIdentifier }}:" update: - describe: "Update an existing HubSpot secret" + describe: "Update an existing secret." + selectSecret: "Select the secret you want to update" errors: update: "The secret \"{{ secretName }}\" was not updated" + noSecret: "Unable to update secret with name \"{{ secretName }}\", it does not exist" positionals: name: describe: "Name of the secret to be updated" @@ -831,7 +880,7 @@ en: update: "The secret \"{{ secretName }}\" was updated in the HubSpot account: {{ accountIdentifier }}" updateExplanation: "Existing serverless functions will start using this new value within 10 seconds." theme: - describe: "Commands for working with themes, including marketplace validation with the marketplace-validate subcommand." + describe: "Commands for managing themes." subcommands: generateSelectors: describe: "Automatically generates an editor-preview.json file for the given theme. The selectors this command generates are not perfect, so please edit editor-preview.json after running." @@ -841,10 +890,10 @@ en: noSelectorsFound: "No selectors found." success: "Selectors generated for {{ themePath }}, please double check the selectors generated at {{ selectorsPath }} before uploading the theme." positionals: - themePath: + path: describe: "The path of the theme you'd like to generate an editor-preview.json for." marketplaceValidate: - describe: "Validate a theme for the marketplace" + describe: "Validate a theme for the marketplace." errors: invalidPath: "The path \"{{ path }}\" is not a path to a folder in the Design Manager" logs: @@ -857,18 +906,19 @@ en: lineNumber: "Line number: {{ line }}" noErrors: "No errors" positionals: - src: + path: describe: "Path to the theme within the Design Manager." preview: - describe: "Upload and watch a theme directory on your computer for changes and start a local development server to preview theme changes on a site" + describe: "Upload and watch a theme directory on your computer for changes and start a local development server to preview theme changes on a site." errors: invalidPath: "The path \"{{ path }}\" is not a path to a directory" noThemeComponents: "Your project has no theme components available to preview." - options: + positionals: src: describe: "Path to the local directory your theme is in, relative to your current working directory" dest: describe: "Path in HubSpot Design Tools. Can be a net new path. If you wish to preview a site page using your theme changes it must match the path of the theme used by the site." + options: notify: describe: "Log to specified file when a watch task is triggered and after workers have gone idle. Ex. --notify path/to/file" noSsl: @@ -935,6 +985,7 @@ en: uploading: "Uploading files from \"{{ src }}\" to \"{{ dest }}\" in the Design Manager of account {{ accountId }}" notUploaded: "There was an error processing \"{{ src }}\". The file has not been uploaded." cleaning: "Removing \"{{ filePath }}\" from account {{ accountId }} and uploading local..." + confirmCleanUpload: "You are about to remove any remote files in \"{{ filePath }}\" on HubSpot account {{ accountId }} that don't exist locally. Are you sure you want to do this?" watch: describe: "Watch a directory on your computer for changes and upload the changed files to the HubSpot CMS." errors: @@ -967,7 +1018,7 @@ en: initialUpload: "To upload the directory run \"hs upload\" beforehand or add the \"--initial-upload\" option when running \"hs watch\"." notUploaded: "The \"hs watch\" command no longer uploads the watched directory when started. The directory \"{{ path }}\" was not uploaded." convertFields: - describe: "Converts a specific JavaScript fields file of a module or theme to JSON" + describe: "Converts a specific JavaScript fields file of a module or theme to JSON." positionals: src: describe: Path to JS Fields file or directory containing javascript fields files. @@ -1045,18 +1096,11 @@ en: checkIfParentAccountIsAuthed: notAuthedError: "To develop this project locally, run {{ authCommand }} to authenticate the App Developer Account {{ accountId }} associated with {{ accountIdentifier }}." projects: - config: + validateProjectConfig: + configNotFound: "Unable to locate a project configuration file. Try running again from a project directory, or run {{ createCommand }} to create a new project." + configMissingFields: "The project configuruation file is missing required fields." + srcDirNotFound: "Project source directory {{#bold}}{{ srcDir }}{{/bold}} could not be found in {{#bold}}{{ projectDir }}{{/bold}}." srcOutsideProjectDir: "Invalid value for 'srcDir' in {{ projectConfig }}: {{#bold}}srcDir: \"{{ srcDir }}\"{{/bold}}\n\t'srcDir' must be a relative path to a folder under the project root, such as \".\" or \"./src\"" - uploadProjectFiles: - add: "Uploading {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" - fail: "Failed to upload {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" - succeed: "Uploaded {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" - buildCreated: "Project \"{{ projectName }}\" uploaded and build #{{ buildId }} created" - handleProjectUpload: - emptySource: "Source directory \"{{ srcDir }}\" is empty. Add files to your project and rerun `{{#yellow}}hs project upload{{/yellow}}` to upload them to HubSpot." - compressed: "Project files compressed: {{ byteCount }} bytes" - compressing: "Compressing build files to \"{{ path }}\"" - fileFiltered: "Ignore rule triggered for \"{{ filename }}\"" ensureProjectExists: createPrompt: "The project {{ projectName }} does not exist in {{ accountIdentifier }}. Would you like to create it?" createPromptUpload: "[--forceCreate] The project {{ projectName }} does not exist in {{ accountIdentifier }}. Would you like to create it?" @@ -1064,6 +1108,11 @@ en: notFound: "Your project {{#bold}}{{ projectName }}{{/bold}} could not be found in {{#bold}}{{ accountIdentifier }}{{/bold}}." pollFetchProject: checkingProject: "Checking if project exists in {{ accountIdentifier }}" + unableToFindAutodeployStatus: "Unable to find the auto deploy for build #{{ buildId }}. This deploy may have been skipped. {{ viewDeploysLink }}." + logFeedbackMessage: + feedbackHeader: "We'd love to hear your feedback!" + feedbackMessage: "How are you liking the new projects and developer tools? \n > Run `{{#yellow}}hs feedback{{/yellow}}` to let us know what you think!\n" + projectBuildAndDeploy: makePollTaskStatusFunc: componentCountSingular: "Found 1 component in this project" componentCount: "Found {{ numComponents }} components in this project" @@ -1075,10 +1124,17 @@ en: buildSucceededAutomaticallyDeploying: "Build #{{ buildId }} succeeded. {{#bold}}Automatically deploying{{/bold}} to {{ accountIdentifier }}\n" cleanedUpTempFile: "Cleaned up temporary file {{ path }}" viewDeploys: "View all deploys for this project in HubSpot" - unableToFindAutodeployStatus: "Unable to find the auto deploy for build #{{ buildId }}. This deploy may have been skipped. {{ viewDeploysLink }}." - logFeedbackMessage: - feedbackHeader: "We'd love to hear your feedback!" - feedbackMessage: "How are you liking the new projects and developer tools? \n > Run `{{#yellow}}hs feedback{{/yellow}}` to let us know what you think!\n" + projectUpload: + uploadProjectFiles: + add: "Uploading {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" + fail: "Failed to upload {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" + succeed: "Uploaded {{#bold}}{{ projectName }}{{/bold}} project files to {{ accountIdentifier }}" + buildCreated: "Project \"{{ projectName }}\" uploaded and build #{{ buildId }} created" + handleProjectUpload: + emptySource: "Source directory \"{{ srcDir }}\" is empty. Add files to your project and rerun `{{#yellow}}hs project upload{{/yellow}}` to upload them to HubSpot." + compressed: "Project files compressed: {{ byteCount }} bytes" + compressing: "Compressing build files to \"{{ path }}\"" + fileFiltered: "Ignore rule triggered for \"{{ filename }}\"" ui: betaTag: "{{#bold}}[BETA]{{/bold}}" betaWarning: @@ -1146,8 +1202,8 @@ en: noLogsFound: "No logs found." commonOpts: options: - portal: - describe: "HubSpot portal id or name from config" + account: + describe: "HubSpot account id or name from config" config: describe: "Path to a config file" overwrite: @@ -1161,6 +1217,8 @@ en: describe: "Run command in QA mode" useEnv: describe: "Use environment variable config" + debug: + describe: "Set log level to debug" prompts: projectDevTargetAccountPrompt: createNewSandboxOption: "" @@ -1172,6 +1230,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: @@ -1217,6 +1276,7 @@ en: selectReactType: "Is this a React module?" selectContentType: "What types of content will this module be used in?" confirmGlobal: "Is this a global module?" + availableForNewContent: "Make this module available for new content?" errors: invalidLabel: "You entered an invalid name. Please try again." labelRequired: "The name may not be blank. Please try again." @@ -1238,12 +1298,12 @@ en: languageRequired: "Please select API sample app's language" createProjectPrompt: enterName: "[--name] Give your project a name: " - enterLocation: "[--location] Enter the folder to create the project in:" + enterDest: "[--dest] Enter the folder to create the project in:" selectTemplate: "[--template] Choose a project template: " errors: nameRequired: "A project name is required" - locationRequired: "A project location is required" - invalidLocation: "The selected destination already exists. Please provide a new path for this project." + destRequired: "A project dest is required" + invalidDest: "The selected destination already exists. Please provide a new path for this project." invalidCharacters: "The selected destination contains invalid characters. Please provide a new path and try again." invalidTemplate: "[--template] Could not find template {{ template }}. Please choose an available template." noProjectsInConfig: "Please ensure that there is a config.json file that contains a \"projects\" field." @@ -1252,6 +1312,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." @@ -1262,6 +1323,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: " @@ -1270,6 +1332,9 @@ en: invalidType: "[--type] Could not find type {{ type }}. Please choose an available type." secretPrompt: enterValue: "Enter a value for your secret: " + enterName: "Enter a name for your secret: " + selectSecretUpdate: "Select the secret you want to update" + selectSecretDelete: "Select the secret you want to delete" errors: invalidValue: "You entered an invalid value. Please try again." sandboxesPrompt: @@ -1280,8 +1345,8 @@ en: developer: "Development sandbox (Includes production's object definitions)" standard: "Standard sandbox (Includes partial copy of production's assets)" uploadPrompt: - enterDest: "[--dest] Enter the destination path: " - enterSrc: "[--src] Enter the source path: " + enterDest: "Enter the destination path: " + enterSrc: "Enter the source path: " errors: srcRequired: "You must specify a source directory." destRequired: "You must specify a destination directory." @@ -1298,14 +1363,21 @@ en: errors: srcRequired: "You must specify a source directory." destRequired: "You must specify a destination directory." - cleanUploadPrompt: - message: "You are about to remove any remote files in \"{{ filePath }}\" on HubSpot account {{ accountId }} that don't exist locally. Are you sure you want to do this?" installPublicAppPrompt: explanation: "Local development requires this app to be installed in the target test account" reinstallExplanation: "This app's required scopes have been updated since it was last installed on the target test account. To avoid issues with local development, we recommend reinstalling the app with the updated scopes." prompt: "Open hubspot.com to install this app?" reinstallPrompt: "Open hubspot.com to reinstall this app?" decline: "To continue local development of this app, install it in your target test account and re-run {{#bold}}`hs project dev`{{/bold}}" + selectHubDBTablePrompt: + selectTable: "Select a HubDB table:" + enterDest: "Enter the destination path:" + errors: + noTables: "No HubDB tables found in account {{ accountId }}" + errorFetchingTables: "Unable to fetch HubDB tables in account {{ accountId }}" + destRequired: "A destination is required" + invalidDest: "The selected destination already exists. Please provide a new path." + invalidCharacters: "The selected destination contains invalid characters. Please provide a new path and try again." convertFields: positionals: src: @@ -1483,6 +1555,8 @@ en: counts: errors: '{{#bold}}Errors:{{/bold}} {{ count }}' warnings: "{{#bold}}Warning:{{/bold}} {{ count }}" + oauth: + missingClientId: "Error building oauth URL: missing client ID." diff --git a/lib/DevServerManager.ts b/lib/DevServerManager.ts index 23c7d392a..525cf5751 100644 --- a/lib/DevServerManager.ts +++ b/lib/DevServerManager.ts @@ -1,6 +1,6 @@ // @ts-nocheck const { logger } = require('@hubspot/local-dev-lib/logger'); -const { COMPONENT_TYPES } = require('./projectStructure'); +const { COMPONENT_TYPES } = require('./projects/structure'); const { i18n } = require('./lang'); const { promptUser } = require('./prompts/promptUtils'); const { DevModeInterface } = require('@hubspot/ui-extensions-dev-server'); diff --git a/lib/LocalDevManager.ts b/lib/LocalDevManager.ts index 40c28e74e..5503f6c10 100644 --- a/lib/LocalDevManager.ts +++ b/lib/LocalDevManager.ts @@ -20,13 +20,13 @@ const { PROJECT_CONFIG_FILE } = require('./constants'); const SpinniesManager = require('./ui/SpinniesManager'); const DevServerManager = require('./DevServerManager'); const { EXIT_CODES } = require('./enums/exitCodes'); -const { getProjectDetailUrl } = require('./projects'); +const { getProjectDetailUrl } = require('./projects/urls'); const { getAccountHomeUrl } = require('./localDev'); const { CONFIG_FILES, COMPONENT_TYPES, getAppCardConfigs, -} = require('./projectStructure'); +} = require('./projects/structure'); const { UI_COLORS, uiCommandReference, @@ -280,10 +280,8 @@ class LocalDevManager { } async checkPublicAppInstallation() { - const { - isInstalledWithScopeGroups, - previouslyAuthorizedScopeGroups, - } = await this.getActiveAppInstallationData(); + const { isInstalledWithScopeGroups, previouslyAuthorizedScopeGroups } = + await this.getActiveAppInstallationData(); const isReinstall = previouslyAuthorizedScopeGroups.length > 0; @@ -364,7 +362,7 @@ class LocalDevManager { monitorConsoleOutput() { const originalStdoutWrite = process.stdout.write.bind(process.stdout); - process.stdout.write = function(chunk, encoding, callback) { + process.stdout.write = function (chunk, encoding, callback) { // Reset the most recently logged warning if ( this.mostRecentUploadWarning && diff --git a/lib/__tests__/ProjectLogsManager.test.ts b/lib/__tests__/ProjectLogsManager.test.ts index c2fcd6da4..2a06371ea 100644 --- a/lib/__tests__/ProjectLogsManager.test.ts +++ b/lib/__tests__/ProjectLogsManager.test.ts @@ -1,5 +1,5 @@ // @ts-nocheck -const ProjectLogsManager = require('../ProjectLogsManager'); +const { ProjectLogsManager } = require('../projects/ProjectLogsManager'); const { getProjectConfig, ensureProjectExists } = require('../projects'); const { fetchProjectComponentsMetadata, @@ -8,7 +8,7 @@ const { jest.mock('../projects'); jest.mock('@hubspot/local-dev-lib/api/projects'); -describe('lib/ProjectLogsManager', () => { +describe('lib/projects/ProjectLogsManager', () => { const accountId = 12345678; const appId = 999999; const projectName = 'super cool test project'; @@ -143,8 +143,8 @@ describe('lib/ProjectLogsManager', () => { }); describe('getFunctionNames', () => { - it('should return an empty array if functions is nullable', async () => { - ProjectLogsManager.functions = undefined; + it('should return an empty array if functions is empty', async () => { + ProjectLogsManager.functions = []; expect(ProjectLogsManager.getFunctionNames()).toEqual([]); }); @@ -158,8 +158,8 @@ describe('lib/ProjectLogsManager', () => { }); describe('setFunction', () => { - it('should throw an error when functions is nullable', async () => { - ProjectLogsManager.functions = undefined; + it('should throw an error when functions is empty', async () => { + ProjectLogsManager.functions = []; expect(() => ProjectLogsManager.setFunction('foo')).toThrow( `There aren't any functions in this project` ); @@ -180,7 +180,7 @@ describe('lib/ProjectLogsManager', () => { name: 'APP_FUNCTION', }, deployOutput: { - endpoint: { path: 'yooooooo', method: ['GET'] }, + endpoint: { path: 'yooooooo', methods: ['GET'] }, }, }; ProjectLogsManager.functions = [functionToChoose]; @@ -188,7 +188,6 @@ describe('lib/ProjectLogsManager', () => { expect(ProjectLogsManager.functionName).toEqual('function1'); expect(ProjectLogsManager.endpointName).toEqual('yooooooo'); expect(ProjectLogsManager.selectedFunction).toEqual(functionToChoose); - expect(ProjectLogsManager.method).toEqual(['GET']); expect(ProjectLogsManager.isPublicFunction).toEqual(true); }); diff --git a/lib/__tests__/commonOpts.test.ts b/lib/__tests__/commonOpts.test.ts index 36a899d63..1354c0ec5 100644 --- a/lib/__tests__/commonOpts.test.ts +++ b/lib/__tests__/commonOpts.test.ts @@ -1,7 +1,7 @@ // @ts-nocheck const { - MODE, - DEFAULT_MODE, + CMS_PUBLISH_MODE, + DEFAULT_CMS_PUBLISH_MODE, } = require('@hubspot/local-dev-lib/constants/files'); const { getAndLoadConfigIfNeeded, @@ -9,13 +9,13 @@ const { getAccountConfig, loadConfigFromEnvironment, } = require('@hubspot/local-dev-lib/config'); -const { getMode } = require('../commonOpts'); +const { getCmsPublishMode } = require('../commonOpts'); jest.mock('@hubspot/local-dev-lib/config'); jest.mock('@hubspot/local-dev-lib/logger'); describe('lib/commonOpts', () => { - describe('getMode()', () => { + describe('getCmsPublishMode()', () => { const accounts = { PROD: 123, DEV: 456, @@ -23,7 +23,7 @@ describe('lib/commonOpts', () => { const devAccountConfig = { accountId: accounts.DEV, name: 'DEV', - defaultMode: MODE.draft, + defaultCmsPublishMode: CMS_PUBLISH_MODE.draft, }; const prodAccountConfig = { accountId: accounts.PROD, @@ -33,9 +33,9 @@ describe('lib/commonOpts', () => { defaultAccount: 'DEV', accounts: [devAccountConfig, prodAccountConfig], }; - const configWithDefaultMode = { + const configWithDefaultCmsPublishMode = { ...config, - defaultMode: MODE.draft, + defaultCmsPublishMode: CMS_PUBLISH_MODE.draft, }; afterEach(() => { @@ -45,38 +45,56 @@ describe('lib/commonOpts', () => { loadConfigFromEnvironment.mockReset(); }); - describe('mode option precedence', () => { - describe('1. --mode', () => { - it('should return the mode specified by the command option if present.', () => { - getAndLoadConfigIfNeeded.mockReturnValue(configWithDefaultMode); + describe('cms publish mode option precedence', () => { + describe('1. --cmsPublishMode', () => { + it('should return the cms publish mode specified by the command option if present.', () => { + getAndLoadConfigIfNeeded.mockReturnValue( + configWithDefaultCmsPublishMode + ); getAccountConfig.mockReturnValue(devAccountConfig); - expect(getMode({ mode: MODE.draft })).toBe(MODE.draft); - expect(getMode({ mode: MODE.publish })).toBe(MODE.publish); - expect(getMode({ mode: 'undefined-mode' })).toBe('undefined-mode'); + expect( + getCmsPublishMode({ cmsPublishMode: CMS_PUBLISH_MODE.draft }) + ).toBe(CMS_PUBLISH_MODE.draft); + expect( + getCmsPublishMode({ cmsPublishMode: CMS_PUBLISH_MODE.publish }) + ).toBe(CMS_PUBLISH_MODE.publish); + expect(getCmsPublishMode({ cmsPublishMode: 'undefined-mode' })).toBe( + 'undefined-mode' + ); }); }); - describe('2. hubspot.config.yml -> config.accounts[x].defaultMode', () => { - it('should return the defaultMode specified by the account specific config if present.', () => { - getAndLoadConfigIfNeeded.mockReturnValue(configWithDefaultMode); + describe('2. hubspot.config.yml -> config.accounts[x].defaultCmsPublishMode', () => { + it('should return the defaultCmsPublishMode specified by the account specific config if present.', () => { + getAndLoadConfigIfNeeded.mockReturnValue( + configWithDefaultCmsPublishMode + ); getAccountId.mockReturnValue(accounts.DEV); getAccountConfig.mockReturnValue(devAccountConfig); loadConfigFromEnvironment.mockReturnValue(undefined); - expect(getMode({ account: accounts.DEV })).toBe(MODE.draft); + expect(getCmsPublishMode({ account: accounts.DEV })).toBe( + CMS_PUBLISH_MODE.draft + ); }); }); - describe('3. hubspot.config.yml -> config.defaultMode', () => { - it('should return the defaultMode specified by the config if present.', () => { - getAndLoadConfigIfNeeded.mockReturnValue(configWithDefaultMode); + describe('3. hubspot.config.yml -> config.defaultCmsPublishMode', () => { + it('should return the defaultCmsPublishMode specified by the config if present.', () => { + getAndLoadConfigIfNeeded.mockReturnValue( + configWithDefaultCmsPublishMode + ); getAccountId.mockReturnValue(accounts.PROD); getAccountConfig.mockReturnValue(prodAccountConfig); loadConfigFromEnvironment.mockReturnValue(undefined); - expect(getMode({ account: accounts.PROD })).toBe(MODE.draft); + expect(getCmsPublishMode({ account: accounts.PROD })).toBe( + CMS_PUBLISH_MODE.draft + ); }); }); - describe('4. DEFAULT_MODE', () => { - it('should return the defaultMode specified by the config if present.', () => { + describe('4. DEFAULT_CMS_PUBLISH_MODE', () => { + it('should return the defaultCmsPubishMode specified by the config if present.', () => { loadConfigFromEnvironment.mockReturnValue(undefined); - expect(getMode({ account: 'xxxxx' })).toBe(DEFAULT_MODE); + expect(getCmsPublishMode({ account: 'xxxxx' })).toBe( + DEFAULT_CMS_PUBLISH_MODE + ); }); }); }); diff --git a/lib/__tests__/projects.test.ts b/lib/__tests__/projects.test.ts index 999da85da..f7f762f4f 100644 --- a/lib/__tests__/projects.test.ts +++ b/lib/__tests__/projects.test.ts @@ -35,7 +35,9 @@ describe('lib/projects', () => { expect(exitMock).toHaveBeenCalledWith(EXIT_CODES.ERROR); expect(logger.error).toHaveBeenCalledWith( - expect.stringMatching(/.*config not found.*/) + expect.stringMatching( + /.*Unable to locate a project configuration file. Try running again from a project directory, or run*/ + ) ); }); diff --git a/lib/buildAccount.ts b/lib/buildAccount.ts index 8704c1221..874f05a2a 100644 --- a/lib/buildAccount.ts +++ b/lib/buildAccount.ts @@ -12,6 +12,9 @@ const { writeConfig, getAccountId, } = require('@hubspot/local-dev-lib/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { i18n } = require('./lang'); const { cliAccountNamePrompt } = require('./prompts/accountNamePrompt'); @@ -53,10 +56,7 @@ async function saveAccountToConfig({ let validName = updatedConfig.name; if (!updatedConfig.name) { - const nameForConfig = accountName - .toLowerCase() - .split(' ') - .join('-'); + const nameForConfig = accountName.toLowerCase().split(' ').join('-'); validName = nameForConfig; const invalidAccountName = accountNameExistsInConfig(nameForConfig); if (invalidAccountName) { @@ -101,7 +101,8 @@ async function buildNewAccount({ SpinniesManager.init({ succeedColor: 'white', }); - const accountId = getAccountId(accountConfig.portalId); + const id = getAccountIdentifier(accountConfig); + const accountId = getAccountId(id); const isSandbox = accountType === HUBSPOT_ACCOUNT_TYPES.STANDARD_SANDBOX || accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX; @@ -161,12 +162,7 @@ async function buildNewAccount({ handleSandboxCreateError({ err, env, accountConfig, name, accountId }); } if (isDeveloperTestAccount) { - handleDeveloperTestAccountCreateError({ - err, - env, - accountId, - portalLimit, - }); + handleDeveloperTestAccountCreateError(err, env, accountId, portalLimit); } } diff --git a/lib/commonOpts.ts b/lib/commonOpts.ts index 06381be59..ba6caf0d7 100644 --- a/lib/commonOpts.ts +++ b/lib/commonOpts.ts @@ -2,7 +2,11 @@ import { LOG_LEVEL, setLogLevel as setLoggerLogLevel, } from '@hubspot/local-dev-lib/logger'; -import { DEFAULT_MODE, MODE } from '@hubspot/local-dev-lib/constants/files'; +import { + DEFAULT_CMS_PUBLISH_MODE, + CMS_PUBLISH_MODE, +} from '@hubspot/local-dev-lib/constants/files'; +import { CmsPublishMode } from '@hubspot/local-dev-lib/types/Files'; import { getAccountId as getAccountIdFromConfig, getAccountConfig, @@ -10,14 +14,24 @@ import { } from '@hubspot/local-dev-lib/config'; import { i18n } from './lang'; import { Argv, Arguments } from 'yargs'; -import { Mode } from '@hubspot/local-dev-lib/types/Files'; const i18nKey = 'lib.commonOpts'; +export function addGlobalOptions(yargs: Argv) { + yargs.version(false); + + return yargs.option('debug', { + alias: 'd', + default: false, + describe: i18n(`${i18nKey}.options.debug.describe`), + type: 'boolean', + }); +} + export function addAccountOptions(yargs: Argv): Argv { - return yargs.option('portal', { - alias: ['p', 'account', 'a'], - describe: i18n(`${i18nKey}.options.portal.describe`), + return yargs.option('account', { + alias: 'a', + describe: i18n(`${i18nKey}.options.account.describe`), type: 'string', }); } @@ -39,19 +53,19 @@ export function addOverwriteOptions(yargs: Argv): Argv { }); } -export function addModeOptions( +export function addCmsPublishModeOptions( yargs: Argv, { read, write }: { read?: boolean; write?: boolean } ): Argv { - const modes = `<${Object.values(MODE).join(' | ')}>`; + const cmsPublishModes = `<${Object.values(CMS_PUBLISH_MODE).join(' | ')}>`; - return yargs.option('mode', { + return yargs.option('cms-publish-mode', { alias: 'm', describe: i18n( `${i18nKey}.options.modes.describe.${ read ? 'read' : write ? 'write' : 'default' }`, - { modes } + { modes: cmsPublishModes } ), type: 'string', }); @@ -67,10 +81,12 @@ export function addTestingOptions(yargs: Argv): Argv { } export function addUseEnvironmentOptions(yargs: Argv): Argv { - return yargs.option('use-env', { - describe: i18n(`${i18nKey}.options.useEnv.describe`), - type: 'boolean', - }); + return yargs + .option('use-env', { + describe: i18n(`${i18nKey}.options.useEnv.describe`), + type: 'boolean', + }) + .conflicts('use-env', 'account'); } export function setLogLevel(options: Arguments<{ debug?: boolean }>): void { @@ -90,46 +106,60 @@ export function getCommandName(argv: Arguments<{ _?: string[] }>): string { * Obtains accountId using supplied --account flag or from environment variables */ export function getAccountId( - options: Arguments<{ portal?: number | string; account?: number | string }> + options: Arguments<{ account?: number | string }> ): number | null { - const { portal, account } = options || {}; + const { account } = options || {}; - if (options?.useEnv && process.env.HUBSPOT_PORTAL_ID) { - return parseInt(process.env.HUBSPOT_PORTAL_ID, 10); + if (options?.useEnv && process.env.HUBSPOT_ACCOUNT_ID) { + return parseInt(process.env.HUBSPOT_ACCOUNT_ID, 10); } - return getAccountIdFromConfig(portal || account); + return getAccountIdFromConfig(account); } -export function getMode(options: Arguments<{ mode?: Mode }>): Mode { - // 1. --mode - const { mode } = options; - if (mode && typeof mode === 'string') { - return mode.toLowerCase() as Mode; +/** + * Auto-injects the derivedAccountId flag into all commands + */ +export async function injectAccountIdMiddleware( + options: Arguments<{ + derivedAccountId?: number | null; + account?: number | string; + }> +): Promise { + const { account } = options; + + // Preserves the original --account flag for certain commands. + options.providedAccountId = account; + + if (options.useEnv && process.env.HUBSPOT_ACCOUNT_ID) { + options.derivedAccountId = parseInt(process.env.HUBSPOT_ACCOUNT_ID, 10); + return; } - // 2. config[portal].defaultMode + + options.derivedAccountId = getAccountIdFromConfig(account); +} + +export function getCmsPublishMode( + options: Arguments<{ cmsPublishMode?: CmsPublishMode }> +): CmsPublishMode { + // 1. --cmsPublishMode + const { cmsPublishMode } = options; + if (cmsPublishMode && typeof cmsPublishMode === 'string') { + return cmsPublishMode.toLowerCase() as CmsPublishMode; + } + // 2. config[account].defaultCmsPublishMode const accountId = getAccountId(options); if (accountId) { const accountConfig = getAccountConfig(accountId); - if (accountConfig && accountConfig.defaultMode) { - return accountConfig.defaultMode; + if (accountConfig && accountConfig.defaultCmsPublishMode) { + return accountConfig.defaultCmsPublishMode; } } - // 3. config.defaultMode - // 4. DEFAULT_MODE + // 3. config.defaultCmsPublishMode + // 4. DEFAULT_CMS_PUBLISH_MODE const config = getAndLoadConfigIfNeeded(); - return (config && (config.defaultMode as Mode)) || DEFAULT_MODE; + return ( + (config && (config.defaultCmsPublishMode as CmsPublishMode)) || + DEFAULT_CMS_PUBLISH_MODE + ); } - -module.exports = { - addAccountOptions, - addConfigOptions, - addOverwriteOptions, - addModeOptions, - addTestingOptions, - addUseEnvironmentOptions, - getCommandName, - getMode, - getAccountId, - setLogLevel, -}; diff --git a/lib/configOptions.ts b/lib/configOptions.ts index 66b21bbf0..180601620 100644 --- a/lib/configOptions.ts +++ b/lib/configOptions.ts @@ -1,23 +1,22 @@ -// @ts-nocheck -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { +import { logger } from '@hubspot/local-dev-lib/logger'; +import { updateAllowUsageTracking, - updateDefaultMode, + updateDefaultCmsPublishMode, updateHttpTimeout, -} = require('@hubspot/local-dev-lib/config'); -const { MODE } = require('@hubspot/local-dev-lib/constants/files'); -const { commaSeparatedValues } = require('@hubspot/local-dev-lib/text'); -const { trackCommandUsage } = require('./usageTracking'); -const { promptUser } = require('./prompts/promptUtils'); -const { i18n } = require('../lib/lang'); +} from '@hubspot/local-dev-lib/config'; +import { CmsPublishMode } from '@hubspot/local-dev-lib/types/Files'; +import { CMS_PUBLISH_MODE } from '@hubspot/local-dev-lib/constants/files'; +import { commaSeparatedValues } from '@hubspot/local-dev-lib/text'; +import { trackCommandUsage } from './usageTracking'; +import { promptUser } from './prompts/promptUtils'; +import { i18n } from '../lib/lang'; const i18nKey = 'commands.config.subcommands.set.options'; -const enableOrDisableUsageTracking = async () => { - const { isEnabled } = await promptUser([ +async function enableOrDisableUsageTracking(): Promise { + const { isEnabled } = await promptUser<{ isEnabled: boolean }>([ { type: 'list', - look: false, name: 'isEnabled', pageSize: 20, message: i18n(`${i18nKey}.allowUsageTracking.promptMessage`), @@ -36,12 +35,18 @@ const enableOrDisableUsageTracking = async () => { ]); return isEnabled; -}; +} -const setAllowUsageTracking = async ({ accountId, allowUsageTracking }) => { - trackCommandUsage('config-set-allow-usage-tracking', null, accountId); +export async function setAllowUsageTracking({ + accountId, + allowUsageTracking, +}: { + accountId: number; + allowUsageTracking?: boolean; +}): Promise { + trackCommandUsage('config-set-allow-usage-tracking', undefined, accountId); - let isEnabled; + let isEnabled: boolean; if (typeof allowUsageTracking === 'boolean') { isEnabled = allowUsageTracking; @@ -51,59 +56,70 @@ const setAllowUsageTracking = async ({ accountId, allowUsageTracking }) => { updateAllowUsageTracking(isEnabled); - return logger.log( - i18n(`${i18nKey}.allowUsageTracking.success`, { isEnabled }) + logger.success( + i18n(`${i18nKey}.allowUsageTracking.success`, { + isEnabled: isEnabled.toString(), + }) ); -}; +} -const ALL_MODES = Object.values(MODE); +const ALL_CMS_PUBLISH_MODES = Object.values(CMS_PUBLISH_MODE); -const selectMode = async () => { - const { mode } = await promptUser([ +async function selectCmsPublishMode(): Promise { + const { cmsPublishMode } = await promptUser<{ + cmsPublishMode: CmsPublishMode; + }>([ { type: 'list', - look: false, - name: 'mode', + name: 'cmsPublishMode', pageSize: 20, message: i18n(`${i18nKey}.defaultMode.promptMessage`), - choices: ALL_MODES, - default: MODE.publish, + choices: ALL_CMS_PUBLISH_MODES, + default: CMS_PUBLISH_MODE.publish, }, ]); - return mode; -}; - -const setDefaultMode = async ({ accountId, defaultMode }) => { - trackCommandUsage('config-set-default-mode', null, accountId); - - let newDefault; - - if (!defaultMode) { - newDefault = await selectMode(); - } else if (defaultMode && ALL_MODES.find(m => m === defaultMode)) { - newDefault = defaultMode; + return cmsPublishMode; +} + +export async function setDefaultCmsPublishMode({ + accountId, + defaultCmsPublishMode, +}: { + accountId: number; + defaultCmsPublishMode?: CmsPublishMode; +}): Promise { + trackCommandUsage('config-set-default-mode', undefined, accountId); + + let newDefault: CmsPublishMode; + + if (!defaultCmsPublishMode) { + newDefault = await selectCmsPublishMode(); + } else if ( + defaultCmsPublishMode && + ALL_CMS_PUBLISH_MODES.find(m => m === defaultCmsPublishMode) + ) { + newDefault = defaultCmsPublishMode; } else { logger.error( - i18n(`${i18nKey}.defaultMode.errors`, { - mode: newDefault, - validModes: commaSeparatedValues(ALL_MODES), + i18n(`${i18nKey}.defaultMode.error`, { + validModes: commaSeparatedValues(ALL_CMS_PUBLISH_MODES), }) ); - newDefault = await selectMode(); + newDefault = await selectCmsPublishMode(); } - updateDefaultMode(newDefault); + updateDefaultCmsPublishMode(newDefault); - return logger.success( + logger.success( i18n(`${i18nKey}.defaultMode.success`, { mode: newDefault, }) ); -}; +} -const enterTimeout = async () => { - const { timeout } = await promptUser([ +async function enterTimeout(): Promise { + const { timeout } = await promptUser<{ timeout: string }>([ { name: 'timeout', message: i18n(`${i18nKey}.httpTimeout.promptMessage`), @@ -113,12 +129,18 @@ const enterTimeout = async () => { ]); return timeout; -}; +} -const setHttpTimeout = async ({ accountId, httpTimeout }) => { - trackCommandUsage('config-set-http-timeout', null, accountId); +export async function setHttpTimeout({ + accountId, + httpTimeout, +}: { + accountId: number; + httpTimeout?: string; +}): Promise { + trackCommandUsage('config-set-http-timeout', undefined, accountId); - let newHttpTimeout; + let newHttpTimeout: string; if (!httpTimeout) { newHttpTimeout = await enterTimeout(); @@ -128,13 +150,7 @@ const setHttpTimeout = async ({ accountId, httpTimeout }) => { updateHttpTimeout(newHttpTimeout); - return logger.success( + logger.success( i18n(`${i18nKey}.httpTimeout.success`, { timeout: newHttpTimeout }) ); -}; - -module.exports = { - setAllowUsageTracking, - setDefaultMode, - setHttpTimeout, -}; +} diff --git a/lib/constants.ts b/lib/constants.ts index 78a6b6ddb..94c30f6ad 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,4 +1,5 @@ -export const HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH = 'HubSpot/hubspot-project-components' as const; +export const HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH = + 'HubSpot/hubspot-project-components' as const; export const DEFAULT_PROJECT_TEMPLATE_BRANCH = 'main' as const; export const FEEDBACK_INTERVAL = 10 as const; @@ -60,12 +61,12 @@ export const PROJECT_ERROR_TYPES = { SUBDEPLOY_FAILED: 'DeployPipelineErrorType.DEPENDENT_SUBDEPLOY_FAILED', } as const; -export const PROJECT_TASK_TYPES = { +export const PROJECT_TASK_TYPES: { [key: string]: string } = { PRIVATE_APP: 'private app', PUBLIC_APP: 'public app', APP_FUNCTION: 'function', CRM_CARD_V2: 'card', -} as const; +}; export const PROJECT_COMPONENT_TYPES = { PROJECTS: 'projects', diff --git a/lib/developerTestAccounts.ts b/lib/developerTestAccounts.ts index 146602ba4..3dcdcbbdc 100644 --- a/lib/developerTestAccounts.ts +++ b/lib/developerTestAccounts.ts @@ -1,25 +1,31 @@ -// @ts-nocheck -const { - HUBSPOT_ACCOUNT_TYPES, -} = require('@hubspot/local-dev-lib/constants/config'); -const { getAccountId, getConfig } = require('@hubspot/local-dev-lib/config'); -const { i18n } = require('./lang'); -const { - fetchDeveloperTestAccounts, -} = require('@hubspot/local-dev-lib/api/developerTestAccounts'); -const { +import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config'; +import { getAccountId, getConfigAccounts } from '@hubspot/local-dev-lib/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { fetchDeveloperTestAccounts } from '@hubspot/local-dev-lib/api/developerTestAccounts'; +import { isMissingScopeError, isSpecifiedError, -} = require('@hubspot/local-dev-lib/errors/index'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { uiAccountDescription } = require('./ui'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { logError } = require('./errorHandlers/index'); +} from '@hubspot/local-dev-lib/errors/index'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; -const getHasDevTestAccounts = appDeveloperAccountConfig => { - const config = getConfig(); - const parentPortalId = getAccountId(appDeveloperAccountConfig.portalId); - for (const portal of config.portals) { +import { i18n } from './lang'; +import { uiAccountDescription } from './ui'; +import { logError } from './errorHandlers/index'; +import { FetchDeveloperTestAccountsResponse } from '@hubspot/local-dev-lib/types/developerTestAccounts'; +import { Environment } from '@hubspot/local-dev-lib/types/Config'; + +function getHasDevTestAccounts(appDeveloperAccountConfig: CLIAccount): boolean { + const id = getAccountIdentifier(appDeveloperAccountConfig); + const parentPortalId = getAccountId(id); + const accountsList = getConfigAccounts(); + + if (!accountsList) { + return false; + } + + for (const portal of accountsList) { if ( Boolean(portal.parentAccountId) && portal.parentAccountId === parentPortalId && @@ -29,14 +35,24 @@ const getHasDevTestAccounts = appDeveloperAccountConfig => { } } return false; -}; +} + +export async function validateDevTestAccountUsageLimits( + accountConfig: CLIAccount +): Promise { + const id = getAccountIdentifier(accountConfig); + const accountId = getAccountId(id); + + if (!accountId) { + return null; + } -const validateDevTestAccountUsageLimits = async accountConfig => { - const accountId = getAccountId(accountConfig.portalId); const { data } = await fetchDeveloperTestAccounts(accountId); + if (!data) { return null; } + const limit = data.maxTestPortals; const count = data.results.length; if (count >= limit) { @@ -58,14 +74,14 @@ const validateDevTestAccountUsageLimits = async accountConfig => { } } return data; -}; +} -function handleDeveloperTestAccountCreateError({ - err, - accountId, - env, - portalLimit, -}) { +export function handleDeveloperTestAccountCreateError( + err: unknown, + accountId: number, + env: Environment, + portalLimit: number +): never { if (isMissingScopeError(err)) { logger.error( i18n('lib.developerTestAccount.create.failure.scopes.message', { @@ -99,8 +115,3 @@ function handleDeveloperTestAccountCreateError({ } throw err; } - -module.exports = { - validateDevTestAccountUsageLimits, - handleDeveloperTestAccountCreateError, -}; diff --git a/lib/doctor/DiagnosticInfoBuilder.ts b/lib/doctor/DiagnosticInfoBuilder.ts index 6c05a38ac..d63c05e89 100644 --- a/lib/doctor/DiagnosticInfoBuilder.ts +++ b/lib/doctor/DiagnosticInfoBuilder.ts @@ -87,12 +87,14 @@ export class DiagnosticInfoBuilder { } async generateDiagnosticInfo(): Promise { - // @ts-expect-error getProjectConfig not typed yet this._projectConfig = await getProjectConfig(); if (this._projectConfig?.projectConfig) { await this.fetchProjectDetails(); await this.fetchAccessToken(); + } + + if (this._projectConfig?.projectDir) { await this.fetchProjectFilenames(); } @@ -134,7 +136,8 @@ export class DiagnosticInfoBuilder { try { const { data } = await fetchProject( this.accountId!, - this._projectConfig?.projectConfig?.name + // We check that config exists before running this function + this._projectConfig!.projectConfig!.name ); this.projectDetails = data; } catch (e) { @@ -158,10 +161,11 @@ export class DiagnosticInfoBuilder { private async fetchProjectFilenames(): Promise { try { - this.files = (await walk(this._projectConfig?.projectDir)) + // We check that projectDir exists before running this function + this.files = (await walk(this._projectConfig!.projectDir!)) .filter(file => !path.dirname(file).includes('node_modules')) .map(filename => - path.relative(this._projectConfig?.projectDir, filename) + path.relative(this._projectConfig!.projectDir!, filename) ); } catch (e) { logger.debug(e); diff --git a/lib/doctor/Doctor.ts b/lib/doctor/Doctor.ts index 34a3cce0e..dea9bdbc3 100644 --- a/lib/doctor/Doctor.ts +++ b/lib/doctor/Doctor.ts @@ -51,7 +51,8 @@ export class Doctor { text: i18n(`${i18nKey}.runningDiagnostics`), }); - this.diagnosticInfo = await this.diagnosticInfoBuilder.generateDiagnosticInfo(); + this.diagnosticInfo = + await this.diagnosticInfoBuilder.generateDiagnosticInfo(); this.projectConfig = this.diagnosticInfo?.project.config; @@ -279,7 +280,7 @@ export class Doctor { const packageDirName = path.dirname(packageFile); try { const needsInstall = await hasMissingPackages( - path.join(this.projectConfig?.projectDir, packageDirName) + path.join(this.projectConfig?.projectDir || '', packageDirName) ); if (needsInstall) { @@ -343,7 +344,10 @@ export class Doctor { private async checkProjectConfigJsonFiles(): Promise { let foundError = false; for (const jsonFile of this.diagnosticInfo?.jsonFiles || []) { - const fileToCheck = path.join(this.projectConfig?.projectDir, jsonFile); + const fileToCheck = path.join( + this.projectConfig?.projectDir || '', + jsonFile + ); if (!(await this.isValidJsonFile(fileToCheck))) { foundError = true; this.diagnosis?.addProjectSection({ diff --git a/lib/doctor/__tests__/Diagnosis.test.ts b/lib/doctor/__tests__/Diagnosis.test.ts index 6fe6ebc1f..a9985c4a4 100644 --- a/lib/doctor/__tests__/Diagnosis.test.ts +++ b/lib/doctor/__tests__/Diagnosis.test.ts @@ -28,6 +28,8 @@ describe('lib/doctor/Diagnosis', () => { projectDir: 'project-dir', projectConfig: { name: 'Super cool project', + srcDir: 'project-dir', + platformVersion: 'test', }, }, }, diff --git a/lib/doctor/__tests__/DiagnosticInfoBuilder.test.ts b/lib/doctor/__tests__/DiagnosticInfoBuilder.test.ts index 34e90cb70..ac741a807 100644 --- a/lib/doctor/__tests__/DiagnosticInfoBuilder.test.ts +++ b/lib/doctor/__tests__/DiagnosticInfoBuilder.test.ts @@ -26,7 +26,7 @@ import { AccessToken, CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; import { getProjectConfig as _getProjectConfig } from '../../projects'; import { fetchProject as _fetchProject } from '@hubspot/local-dev-lib/api/projects'; import { Project } from '@hubspot/local-dev-lib/types/Project'; -import { AxiosPromise } from 'axios'; +import { HubSpotPromise } from '@hubspot/local-dev-lib/types/Http'; const walk = _walk as jest.MockedFunction; const getAccessToken = _getAccessToken as jest.MockedFunction< @@ -124,6 +124,8 @@ describe('lib/doctor/DiagnosticInfo', () => { projectDir, projectConfig: { name: 'My project', + srcDir: 'project-dir', + platformVersion: 'test', }, }; @@ -133,7 +135,7 @@ describe('lib/doctor/DiagnosticInfo', () => { deployedBuildId: 1, id: 8989898, isLocked: false, - name: projectConfig.projectConfig.name, + name: projectConfig!.projectConfig!.name, portalId: accountId, updatedAt: 12345, }; @@ -143,16 +145,16 @@ describe('lib/doctor/DiagnosticInfo', () => { accountType: 'STANDARD', encodedOAuthRefreshToken: '', expiresAt: '', - hubName: projectConfig.projectConfig.name, + hubName: projectConfig!.projectConfig!.name, portalId: accountId, scopeGroups: [], enabledFeatures: {}, }; getProjectConfig.mockResolvedValue(projectConfig); - fetchProject.mockResolvedValue(({ + fetchProject.mockResolvedValue({ data: projectDetails, - } as unknown) as AxiosPromise); + } as unknown as HubSpotPromise); getAccessToken.mockResolvedValue(accessToken); getConfigPath.mockReturnValue(configPath); utilPromisify.mockReturnValue(jest.fn().mockResolvedValue(npmVersion)); @@ -166,7 +168,7 @@ describe('lib/doctor/DiagnosticInfo', () => { expect(fetchProject).toHaveBeenCalledTimes(1); expect(fetchProject).toHaveBeenCalledWith( accountId, - projectConfig!.projectConfig.name + projectConfig!.projectConfig!.name ); expect(getAccessToken).toHaveBeenCalledTimes(1); diff --git a/lib/doctor/__tests__/Doctor.test.ts b/lib/doctor/__tests__/Doctor.test.ts index cf6fc19ef..18977976e 100644 --- a/lib/doctor/__tests__/Doctor.test.ts +++ b/lib/doctor/__tests__/Doctor.test.ts @@ -28,17 +28,19 @@ jest.mock('util', () => ({ const hasMissingPackages = _hasMissingPackages as jest.MockedFunction< typeof _hasMissingPackages >; -const isPortManagerPortAvailable = _isPortManagerPortAvailable as jest.MockedFunction< - typeof _isPortManagerPortAvailable ->; +const isPortManagerPortAvailable = + _isPortManagerPortAvailable as jest.MockedFunction< + typeof _isPortManagerPortAvailable + >; const utilPromisify = util.promisify as jest.MockedFunction< typeof util.promisify >; -const accessTokenForPersonalAccessKey = _accessTokenForPersonalAccessKey as jest.MockedFunction< - typeof _accessTokenForPersonalAccessKey ->; +const accessTokenForPersonalAccessKey = + _accessTokenForPersonalAccessKey as jest.MockedFunction< + typeof _accessTokenForPersonalAccessKey + >; const isSpecifiedError = _isSpecifiedError as jest.MockedFunction< typeof _isSpecifiedError @@ -64,7 +66,11 @@ describe('lib/doctor/Doctor', () => { project: { config: { projectDir: '/path/to/project', - projectConfig: {}, + projectConfig: { + name: 'my-project', + srcDir: '/path/to/project', + platformVersion: 'test', + }, }, }, versions: { @@ -75,11 +81,11 @@ describe('lib/doctor/Doctor', () => { }; beforeEach(() => { - doctor = new Doctor(({ + doctor = new Doctor({ generateDiagnosticInfo: jest.fn().mockResolvedValue({ ...diagnosticInfo, }), - } as unknown) as DiagnosticInfoBuilder); + } as unknown as DiagnosticInfoBuilder); utilPromisify.mockReturnValue( jest.fn().mockImplementationOnce((filename: string) => { @@ -104,12 +110,12 @@ describe('lib/doctor/Doctor', () => { }); it('should add error section if node version is not available', async () => { - doctor = new Doctor(({ + doctor = new Doctor({ generateDiagnosticInfo: jest.fn().mockResolvedValue({ ...diagnosticInfo, versions: {}, }), - } as unknown) as DiagnosticInfoBuilder); + } as unknown as DiagnosticInfoBuilder); await doctor.diagnose(); @@ -121,12 +127,12 @@ describe('lib/doctor/Doctor', () => { }); it('should add error section if minimum node version is not met', async () => { - doctor = new Doctor(({ + doctor = new Doctor({ generateDiagnosticInfo: jest.fn().mockResolvedValue({ ...diagnosticInfo, versions: { node: '1.0.0' }, }), - } as unknown) as DiagnosticInfoBuilder); + } as unknown as DiagnosticInfoBuilder); await doctor.diagnose(); @@ -149,12 +155,12 @@ describe('lib/doctor/Doctor', () => { }); it('should add error section if npm is not installed', async () => { - doctor = new Doctor(({ + doctor = new Doctor({ generateDiagnosticInfo: jest.fn().mockResolvedValue({ ...diagnosticInfo, versions: {}, }), - } as unknown) as DiagnosticInfoBuilder); + } as unknown as DiagnosticInfoBuilder); await doctor.diagnose(); diff --git a/lib/doctor/__tests__/__snapshots__/DiagnosticInfoBuilder.test.ts.snap b/lib/doctor/__tests__/__snapshots__/DiagnosticInfoBuilder.test.ts.snap index 46dcb2477..4bef91f57 100644 --- a/lib/doctor/__tests__/__snapshots__/DiagnosticInfoBuilder.test.ts.snap +++ b/lib/doctor/__tests__/__snapshots__/DiagnosticInfoBuilder.test.ts.snap @@ -58,6 +58,8 @@ exports[`lib/doctor/DiagnosticInfo generateDiagnosticInfo should gather the requ "config": { "projectConfig": { "name": "My project", + "platformVersion": "test", + "srcDir": "project-dir", }, "projectDir": "/Users/test/project", }, diff --git a/lib/generateSelectors.ts b/lib/generateSelectors.ts index c5d54bc65..0427e9a08 100644 --- a/lib/generateSelectors.ts +++ b/lib/generateSelectors.ts @@ -1,8 +1,8 @@ -// @ts-nocheck -const fs = require('fs'); -const { EXIT_CODES } = require('./enums/exitCodes'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { i18n } = require('./lang'); +import fs from 'fs'; +import { logger } from '@hubspot/local-dev-lib/logger'; + +import { EXIT_CODES } from './enums/exitCodes'; +import { i18n } from './lang'; const CSS_COMMENTS_REGEX = new RegExp(/\/\*.*\*\//, 'g'); const CSS_PSEUDO_CLASS_REGEX = new RegExp( @@ -13,11 +13,11 @@ const i18nKey = 'commands.theme.subcommands.generateSelectors'; let maxFieldsDepth = 0; -function getMaxFieldsDepth() { +export function getMaxFieldsDepth(): number { return maxFieldsDepth; } -function findFieldsJsonPath(basePath) { +export function findFieldsJsonPath(basePath: string): string | null { const _path = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; if (!fs.existsSync(_path)) { logger.error( @@ -46,7 +46,10 @@ function findFieldsJsonPath(basePath) { return null; } -function combineThemeCss(basePath, cssString = '') { +export function combineThemeCss( + basePath: string, + cssString = '' +): string | null { const _path = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; const isDirectory = fs.lstatSync(_path).isDirectory(); @@ -63,7 +66,23 @@ function combineThemeCss(basePath, cssString = '') { return null; } -function setPreviewSelectors(fields, fieldPath, selectors, depth = 0) { +type Field = { + name: string; + children: Field[]; + selectors?: string[]; + inherited_value?: { + property_value_paths?: { + [key: string]: string; + }; + }; +}; + +export function setPreviewSelectors( + fields: Field[], + fieldPath: string[], + selectors: string[], + depth = 0 +): Field[] { fields.forEach((field, index) => { if (field.name === fieldPath[0]) { if (field.children && fieldPath.length > 0) { @@ -81,7 +100,7 @@ function setPreviewSelectors(fields, fieldPath, selectors, depth = 0) { } selectors.forEach(selector => { - const fieldSelectors = field.selectors; + const fieldSelectors = field.selectors!; selector = selector.replace(CSS_COMMENTS_REGEX, ''); selector = selector.replace(CSS_PSEUDO_CLASS_REGEX, '').trim(); @@ -99,10 +118,10 @@ function setPreviewSelectors(fields, fieldPath, selectors, depth = 0) { return fields; } -function generateInheritedSelectors(fields) { +export function generateInheritedSelectors(fields: Field[]): Field[] { let finalFieldsJson = [...fields]; - const _generateInheritedSelectors = fieldsToCheck => { + function _generateInheritedSelectors(fieldsToCheck: Field[]): void { fieldsToCheck.forEach(field => { if (field.children) { _generateInheritedSelectors(field.children); @@ -125,15 +144,20 @@ function generateInheritedSelectors(fields) { }); } }); - }; + } _generateInheritedSelectors(fields); return finalFieldsJson; } -function generateSelectorsMap(fields, fieldKey = []) { - let selectorsMap = {}; +type SelectorsMap = { [key: string]: string[] | undefined }; + +export function generateSelectorsMap( + fields: Field[], + fieldKey: string[] = [] +): SelectorsMap { + let selectorsMap: SelectorsMap = {}; fields.forEach(field => { const { children, name, selectors } = field; @@ -151,12 +175,3 @@ function generateSelectorsMap(fields, fieldKey = []) { return selectorsMap; } - -module.exports = { - findFieldsJsonPath, - combineThemeCss, - setPreviewSelectors, - generateInheritedSelectors, - generateSelectorsMap, - getMaxFieldsDepth, -}; diff --git a/lib/interpolation.ts b/lib/interpolation.ts index 8aae5304c..b551b8776 100644 --- a/lib/interpolation.ts +++ b/lib/interpolation.ts @@ -1,22 +1,22 @@ import chalk from 'chalk'; export const helpers: { [key: string]: (stringValue: string) => string } = { - bold: function(stringValue: string): string { + bold: function (stringValue: string): string { return chalk.bold(stringValue); }, - yellow: function(stringValue: string): string { + yellow: function (stringValue: string): string { return chalk.reset.yellow(stringValue); }, - green: function(stringValue: string): string { + green: function (stringValue: string): string { return chalk.reset.green(stringValue); }, - red: function(stringValue: string): string { + red: function (stringValue: string): string { return chalk.reset.red(stringValue); }, - cyan: function(stringValue: string): string { + cyan: function (stringValue: string): string { return chalk.cyan(stringValue); }, - orange: function(stringValue: string): string { + orange: function (stringValue: string): string { return chalk.hex('#FC9900')(stringValue); }, }; diff --git a/lib/localDev.ts b/lib/localDev.ts index d983cad74..0f9d7ebd2 100644 --- a/lib/localDev.ts +++ b/lib/localDev.ts @@ -38,10 +38,8 @@ const { isAppDeveloperAccount, isDeveloperTestAccount, } = require('./accountTypes'); -const { - handleProjectUpload, - pollProjectBuildAndDeploy, -} = require('./projects'); +const { handleProjectUpload } = require('./projects/upload'); +const { pollProjectBuildAndDeploy } = require('./projects/buildAndDeploy'); const { PROJECT_ERROR_TYPES, PROJECT_BUILD_TEXT, @@ -248,9 +246,8 @@ const createDeveloperTestAccountForLocalDev = async ( let currentPortalCount = 0; let maxTestPortals = 10; try { - const validateResult = await validateDevTestAccountUsageLimits( - accountConfig - ); + const validateResult = + await validateDevTestAccountUsageLimits(accountConfig); if (validateResult) { currentPortalCount = validateResult.results ? validateResult.results.length @@ -306,9 +303,8 @@ const createDeveloperTestAccountForLocalDev = async ( // Prompt user to confirm usage of an existing developer test account that is not currently in the config const useExistingDevTestAccount = async (env, account) => { - const useExistingDevTestAcct = await confirmUseExistingDeveloperTestAccountPrompt( - account - ); + const useExistingDevTestAcct = + await confirmUseExistingDeveloperTestAccountPrompt(account); if (!useExistingDevTestAcct) { logger.log(''); logger.log( diff --git a/lib/marketplaceValidate.ts b/lib/marketplaceValidate.ts index af8f5e9d2..6702a38a6 100644 --- a/lib/marketplaceValidate.ts +++ b/lib/marketplaceValidate.ts @@ -1,18 +1,25 @@ -// @ts-nocheck -const chalk = require('chalk'); - -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { +import chalk from 'chalk'; +import { requestValidation, getValidationStatus, getValidationResults, -} = require('@hubspot/local-dev-lib/api/marketplaceValidation'); -const { i18n } = require('./lang'); -const { EXIT_CODES } = require('./enums/exitCodes'); +} from '@hubspot/local-dev-lib/api/marketplaceValidation'; +import { logger } from '@hubspot/local-dev-lib/logger'; + +import { i18n } from './lang'; +import { EXIT_CODES } from './enums/exitCodes'; +import { + Check, + GetValidationResultsResponse, +} from '@hubspot/local-dev-lib/types/MarketplaceValidation'; const SLEEP_TIME = 2000; -const kickOffValidation = async (accountId, assetType, src) => { +export async function kickOffValidation( + accountId: number, + assetType: string, + src: string +): Promise { const requestGroup = 'EXTERNAL_DEVELOPER'; try { const { data: requestResult } = await requestValidation(accountId, { @@ -25,9 +32,12 @@ const kickOffValidation = async (accountId, assetType, src) => { logger.debug(err); process.exit(EXIT_CODES.ERROR); } -}; +} -const pollForValidationFinish = async (accountId, validationId) => { +export async function pollForValidationFinish( + accountId: number, + validationId: string +): Promise { try { const checkValidationStatus = async () => { const { data: validationStatus } = await getValidationStatus(accountId, { @@ -44,9 +54,12 @@ const pollForValidationFinish = async (accountId, validationId) => { logger.debug(err); process.exit(EXIT_CODES.ERROR); } -}; +} -const fetchValidationResults = async (accountId, validationId) => { +export async function fetchValidationResults( + accountId: number, + validationId: string +): Promise { try { const { data: validationResults } = await getValidationResults(accountId, { validationId, @@ -56,9 +69,12 @@ const fetchValidationResults = async (accountId, validationId) => { logger.debug(err); process.exit(EXIT_CODES.ERROR); } -}; +} -const processValidationErrors = (i18nKey, validationResults) => { +export function processValidationErrors( + i18nKey: string, + validationResults: GetValidationResultsResponse +): void { if (validationResults.errors.length) { const { assetPath, errors } = validationResults; @@ -75,72 +91,73 @@ const processValidationErrors = (i18nKey, validationResults) => { }); process.exit(EXIT_CODES.ERROR); } +} + +function displayFileInfo( + file: string, + line: number | null, + i18nKey: string +): void { + if (file) { + logger.log( + i18n(`${i18nKey}.results.warnings.file`, { + file, + }) + ); + } + if (line) { + logger.log( + i18n(`${i18nKey}.results.warnings.lineNumber`, { + line, + }) + ); + } +} + +type Result = { + status: string; + results: Check[]; }; -const displayValidationResults = (i18nKey, validationResults) => { - const displayResults = checks => { - const displayFileInfo = (file, line) => { - if (file) { - logger.log( - i18n(`${i18nKey}.results.warnings.file`, { - file, - }) - ); - } - if (line) { - logger.log( - i18n(`${i18nKey}.results.warnings.lineNumber`, { - line, - }) - ); - } - return null; - }; +type ValidationType = keyof GetValidationResultsResponse['results']; - if (checks) { - const { status, results } = checks; +function displayResults(checks: Result, i18nKey: string): void { + if (checks) { + const { status, results } = checks; - if (status === 'FAIL') { - const failedValidations = results.filter( - test => test.status === 'FAIL' - ); - const warningValidations = results.filter( - test => test.status === 'WARN' - ); - failedValidations.forEach(val => { - logger.error(`${val.message}`); - displayFileInfo(val.file, val.line); - }); - warningValidations.forEach(val => { - logger.warn(`${val.message}`); - displayFileInfo(val.file, val.line); - }); - } - if (status === 'PASS') { - logger.success(i18n(`${i18nKey}.results.noErrors`)); + if (status === 'FAIL') { + const failedValidations = results.filter(test => test.status === 'FAIL'); + const warningValidations = results.filter(test => test.status === 'WARN'); + failedValidations.forEach(val => { + logger.error(`${val.message}`); + displayFileInfo(val.file, val.line, i18nKey); + }); + warningValidations.forEach(val => { + logger.warn(`${val.message}`); + displayFileInfo(val.file, val.line, i18nKey); + }); + } + if (status === 'PASS') { + logger.success(i18n(`${i18nKey}.results.noErrors`)); - results.forEach(test => { - if (test.status === 'WARN') { - logger.warn(`${test.message}`); - displayFileInfo(test.file, test.line); - } - }); - } + results.forEach(test => { + if (test.status === 'WARN') { + logger.warn(`${test.message}`); + displayFileInfo(test.file, test.line, i18nKey); + } + }); } - return null; - }; + } + return; +} +export function displayValidationResults( + i18nKey: string, + validationResults: GetValidationResultsResponse +) { Object.keys(validationResults.results).forEach(type => { logger.log(chalk.bold(i18n(`${i18nKey}.results.${type.toLowerCase()}`))); - displayResults(validationResults.results[type]); + displayResults(validationResults.results[type as ValidationType], i18nKey); logger.log(); }); -}; - -module.exports = { - kickOffValidation, - pollForValidationFinish, - fetchValidationResults, - processValidationErrors, - displayValidationResults, -}; +} diff --git a/lib/oauth.ts b/lib/oauth.ts index afb1bdbe6..875ec08a4 100644 --- a/lib/oauth.ts +++ b/lib/oauth.ts @@ -1,48 +1,66 @@ -// @ts-nocheck -const express = require('express'); -const open = require('open'); -const { - OAuth2Manager, -} = require('@hubspot/local-dev-lib/models/OAuth2Manager'); -const { getAccountConfig } = require('@hubspot/local-dev-lib/config'); -const { addOauthToAccountConfig } = require('@hubspot/local-dev-lib/oauth'); -const { handleExit } = require('./process'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { - ENVIRONMENTS, -} = require('@hubspot/local-dev-lib/constants/environments'); +import express from 'express'; +import open from 'open'; +import { OAuth2Manager } from '@hubspot/local-dev-lib/models/OAuth2Manager'; +import { getAccountConfig } from '@hubspot/local-dev-lib/config'; +import { getAccountIdentifier } from '@hubspot/local-dev-lib/config/getAccountIdentifier'; +import { addOauthToAccountConfig } from '@hubspot/local-dev-lib/oauth'; +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments'; +import { DEFAULT_OAUTH_SCOPES } from '@hubspot/local-dev-lib/constants/auth'; +import { OAuth2ManagerAccountConfig } from '@hubspot/local-dev-lib/types/Accounts'; +import { Server } from 'http'; + +import { handleExit } from './process'; +import { i18n } from './lang'; +import { EXIT_CODES } from './enums/exitCodes'; const PORT = 3000; const redirectUri = `http://localhost:${PORT}/oauth-callback`; -const buildAuthUrl = oauthManager => { +const i18nKey = 'lib.oauth'; + +function buildAuthUrl(oauthManager: OAuth2Manager): string { + const { + env: accountEnv, + clientId, + scopes: accountScopes, + } = oauthManager.account; + + const env = accountEnv || ENVIRONMENTS.PROD; + const scopes = accountScopes || DEFAULT_OAUTH_SCOPES; + + if (!clientId) { + logger.error(i18n(`${i18nKey}.missingClientId`)); + process.exit(EXIT_CODES.ERROR); + } + return ( - `${getHubSpotWebsiteOrigin(oauthManager.account.env)}/oauth/${ + `${getHubSpotWebsiteOrigin(env)}/oauth/${ oauthManager.account.accountId }/authorize` + - `?client_id=${encodeURIComponent(oauthManager.account.clientId)}` + // app's client ID - `&scope=${encodeURIComponent(oauthManager.account.scopes.join(' '))}` + // scopes being requested by the app + `?client_id=${encodeURIComponent(clientId)}` + // app's client ID + `&scope=${encodeURIComponent(scopes.join(' '))}` + // scopes being requested by the app `&redirect_uri=${encodeURIComponent(redirectUri)}` // where to send the user after the consent page ); -}; +} -const handleServerOnProcessEnd = server => { +function handleServerOnProcessEnd(server: Server): void { const shutdownServerIfRunning = () => { server?.close(); }; handleExit(shutdownServerIfRunning); -}; +} -const authorize = async oauthManager => { +async function authorize(oauthManager: OAuth2Manager): Promise { if (process.env.BROWSER !== 'none') { open(buildAuthUrl(oauthManager), { url: true }); } // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve, reject) => { - let server; + return new Promise(async (resolve, reject) => { + let server: Server | null; const app = express(); app.get('/oauth-callback', async (req, res) => { @@ -84,24 +102,22 @@ const authorize = async oauthManager => { handleServerOnProcessEnd(server); }); -}; +} -const setupOauth = accountConfig => { - const accountId = parseInt(accountConfig.portalId, 10); - const config = getAccountConfig(accountId) || {}; +function setupOauth(accountConfig: OAuth2ManagerAccountConfig): OAuth2Manager { + const accountId = getAccountIdentifier(accountConfig); + const config = getAccountConfig(accountId); return new OAuth2Manager({ ...accountConfig, - environment: accountConfig.env || config.env || ENVIRONMENTS.PROD, + env: accountConfig.env || config?.env || ENVIRONMENTS.PROD, }); -}; +} -const authenticateWithOauth = async configData => { - const oauthManager = setupOauth(configData); +export async function authenticateWithOauth( + accountConfig: OAuth2ManagerAccountConfig +): Promise { + const oauthManager = setupOauth(accountConfig); logger.log('Authorizing'); await authorize(oauthManager); addOauthToAccountConfig(oauthManager); -}; - -module.exports = { - authenticateWithOauth, -}; +} diff --git a/lib/polling.ts b/lib/polling.ts index a62d2bbd6..baced84f2 100644 --- a/lib/polling.ts +++ b/lib/polling.ts @@ -1,7 +1,21 @@ -// @ts-nocheck -const { POLLING_DELAY, POLLING_STATUS } = require('./constants'); +import { HubSpotPromise } from '@hubspot/local-dev-lib/types/Http'; +import { ValueOf } from '@hubspot/local-dev-lib/types/Utils'; +import { POLLING_DELAY, POLLING_STATUS } from './constants'; -const poll = (callback, accountId, taskId) => { +type GenericPollingResponse = { + status: ValueOf; +}; + +type PollingCallback = ( + accountId: number, + taskId: number | string +) => HubSpotPromise; + +export function poll( + callback: PollingCallback, + accountId: number, + taskId: number | string +): Promise { return new Promise((resolve, reject) => { const pollInterval = setInterval(async () => { try { @@ -25,8 +39,4 @@ const poll = (callback, accountId, taskId) => { } }, POLLING_DELAY); }); -}; - -module.exports = { - poll, -}; +} diff --git a/lib/projectStructure.ts b/lib/projectStructure.ts deleted file mode 100644 index dffb98af6..000000000 --- a/lib/projectStructure.ts +++ /dev/null @@ -1,137 +0,0 @@ -// @ts-nocheck -const fs = require('fs'); -const path = require('path'); -const { walk } = require('@hubspot/local-dev-lib/fs'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { logError } = require('./errorHandlers/index'); - -const COMPONENT_TYPES = Object.freeze({ - privateApp: 'private-app', - publicApp: 'public-app', - hublTheme: 'hubl-theme', -}); - -const CONFIG_FILES = { - [COMPONENT_TYPES.privateApp]: 'app.json', - [COMPONENT_TYPES.publicApp]: 'public-app.json', - [COMPONENT_TYPES.hublTheme]: 'theme.json', -}; - -function getTypeFromConfigFile(configFile) { - for (const key in CONFIG_FILES) { - if (CONFIG_FILES[key] === configFile) { - return key; - } - } - return null; -} - -function loadConfigFile(configPath) { - if (configPath) { - try { - const source = fs.readFileSync(configPath); - const parsedConfig = JSON.parse(source); - return parsedConfig; - } catch (e) { - logger.debug(e); - } - } - return null; -} - -function getAppCardConfigs(appConfig, appPath) { - const cardConfigs = []; - let cards; - - if (appConfig && appConfig.extensions && appConfig.extensions.crm) { - cards = appConfig.extensions.crm.cards; - } - - if (cards) { - cards.forEach(({ file }) => { - if (typeof file === 'string') { - const cardConfigPath = path.join(appPath, file); - const cardConfig = loadConfigFile(cardConfigPath); - - if (cardConfig) { - cardConfigs.push(cardConfig); - } - } - }); - } - - return cardConfigs; -} - -function getIsLegacyApp(appConfig, appPath) { - const cardConfigs = getAppCardConfigs(appConfig, appPath); - - if (!cardConfigs.length) { - // Assume any app that does not have any cards is not legacy - return false; - } - - let hasAnyReactExtensions = false; - - cardConfigs.forEach(cardConfig => { - if (!hasAnyReactExtensions) { - const isReactExtension = - cardConfig && - !!cardConfig.data && - !!cardConfig.data.module && - !!cardConfig.data.module.file; - - hasAnyReactExtensions = isReactExtension; - } - }); - - return !hasAnyReactExtensions; -} - -async function findProjectComponents(projectSourceDir) { - const components = []; - let projectFiles = []; - - try { - projectFiles = await walk(projectSourceDir); - } catch (e) { - logError(e); - } - - projectFiles.forEach(projectFile => { - // Find app components - const { base, dir } = path.parse(projectFile); - - if (Object.values(CONFIG_FILES).includes(base)) { - const parsedAppConfig = loadConfigFile(projectFile); - - if (parsedAppConfig) { - const isLegacy = getIsLegacyApp(parsedAppConfig, dir); - const isHublTheme = base === CONFIG_FILES[COMPONENT_TYPES.hublTheme]; - - components.push({ - type: getTypeFromConfigFile(base), - config: parsedAppConfig, - runnable: !isLegacy && !isHublTheme, - path: dir, - }); - } - } - }); - - return components; -} - -function getProjectComponentTypes(components) { - const projectContents = {}; - components.forEach(({ type }) => (projectContents[type] = true)); - return projectContents; -} - -module.exports = { - CONFIG_FILES, - COMPONENT_TYPES, - findProjectComponents, - getAppCardConfigs, - getProjectComponentTypes, -}; diff --git a/lib/projects.ts b/lib/projects.ts deleted file mode 100644 index 658df28f7..000000000 --- a/lib/projects.ts +++ /dev/null @@ -1,1048 +0,0 @@ -// @ts-nocheck -const fs = require('fs-extra'); -const path = require('path'); -const archiver = require('archiver'); -const tmp = require('tmp'); -const chalk = require('chalk'); -const findup = require('findup-sync'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { getEnv } = require('@hubspot/local-dev-lib/config'); -const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); -const { fetchFileFromRepository } = require('@hubspot/local-dev-lib/github'); -const { - ENVIRONMENTS, -} = require('@hubspot/local-dev-lib/constants/environments'); -const { - FEEDBACK_INTERVAL, - POLLING_DELAY, - PROJECT_BUILD_TEXT, - PROJECT_DEPLOY_TEXT, - PROJECT_CONFIG_FILE, - PROJECT_TASK_TYPES, - PROJECT_ERROR_TYPES, - HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, - PROJECT_COMPONENT_TYPES, -} = require('./constants'); -const { - createProject, - getBuildStatus, - getBuildStructure, - getDeployStatus, - getDeployStructure, - fetchProject, - uploadProject, - fetchBuildWarnLogs, - fetchDeployWarnLogs, -} = require('@hubspot/local-dev-lib/api/projects'); -const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); -const { shouldIgnoreFile } = require('@hubspot/local-dev-lib/ignoreRules'); -const { getCwd, getAbsoluteFilePath } = require('@hubspot/local-dev-lib/path'); -const { downloadGithubRepoContents } = require('@hubspot/local-dev-lib/github'); -const { promptUser } = require('./prompts/promptUtils'); -const { EXIT_CODES } = require('./enums/exitCodes'); -const { uiLine, uiLink, uiAccountDescription } = require('./ui'); -const { i18n } = require('./lang'); -const SpinniesManager = require('./ui/SpinniesManager'); -const { logError, ApiErrorContext } = require('./errorHandlers/index'); - -const i18nKey = 'lib.projects'; - -const SPINNER_STATUS = { - SPINNING: 'spinning', -}; - -const writeProjectConfig = (configPath, config) => { - try { - fs.ensureFileSync(configPath); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - logger.debug(`Wrote project config at ${configPath}`); - } catch (e) { - logger.debug(e); - return false; - } - return true; -}; - -const getIsInProject = _dir => { - const configPath = getProjectConfigPath(_dir); - return !!configPath; -}; - -const getProjectConfigPath = _dir => { - const projectDir = _dir ? getAbsoluteFilePath(_dir) : getCwd(); - - const configPath = findup(PROJECT_CONFIG_FILE, { - cwd: projectDir, - nocase: true, - }); - - return configPath; -}; - -export const getProjectConfig = async _dir => { - const configPath = await getProjectConfigPath(_dir); - if (!configPath) { - return { projectConfig: null, projectDir: null }; - } - - try { - const config = fs.readFileSync(configPath); - const projectConfig = JSON.parse(config); - return { - projectDir: path.dirname(configPath), - projectConfig, - }; - } catch (e) { - logger.error('Could not read from project config'); - } -}; - -const createProjectConfig = async ( - projectPath, - projectName, - template, - templateSource, - githubRef -) => { - const { projectConfig, projectDir } = await getProjectConfig(projectPath); - - if (projectConfig) { - logger.warn( - projectPath === projectDir - ? 'A project already exists in that location.' - : `Found an existing project definition in ${projectDir}.` - ); - - const { shouldContinue } = await promptUser([ - { - name: 'shouldContinue', - message: () => { - return projectPath === projectDir - ? 'Do you want to overwrite the existing project definition with a new one?' - : `Continue creating a new project in ${projectPath}?`; - }, - type: 'confirm', - default: false, - }, - ]); - - if (!shouldContinue) { - return false; - } - } - - const projectConfigPath = path.join(projectPath, PROJECT_CONFIG_FILE); - - logger.log( - `Creating project config in ${ - projectPath ? projectPath : 'the current folder' - }` - ); - - const hasCustomTemplateSource = Boolean(templateSource); - - await downloadGithubRepoContents( - templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, - template.path, - projectPath, - hasCustomTemplateSource ? undefined : githubRef - ); - const _config = JSON.parse(fs.readFileSync(projectConfigPath)); - writeProjectConfig(projectConfigPath, { - ..._config, - name: projectName, - }); - - if (template.name === 'no-template') { - fs.ensureDirSync(path.join(projectPath, 'src')); - } - - return true; -}; - -const validateProjectConfig = (projectConfig, projectDir) => { - if (!projectConfig) { - logger.error( - `Project config not found. Try running 'hs project create' first.` - ); - return process.exit(EXIT_CODES.ERROR); - } - - if (!projectConfig.name || !projectConfig.srcDir) { - logger.error( - 'Project config is missing required fields. Try running `hs project create`.' - ); - return process.exit(EXIT_CODES.ERROR); - } - - const resolvedPath = path.resolve(projectDir, projectConfig.srcDir); - if (!resolvedPath.startsWith(projectDir)) { - const projectConfigFile = path.relative( - '.', - path.join(projectDir, PROJECT_CONFIG_FILE) - ); - logger.error( - i18n(`${i18nKey}.config.srcOutsideProjectDir`, { - srcDir: projectConfig.srcDir, - projectConfig: projectConfigFile, - }) - ); - return process.exit(EXIT_CODES.ERROR); - } - - if (!fs.existsSync(resolvedPath)) { - logger.error( - `Project source directory '${projectConfig.srcDir}' could not be found in ${projectDir}.` - ); - return process.exit(EXIT_CODES.ERROR); - } -}; - -const pollFetchProject = async (accountId, projectName) => { - // Temporary solution for gating slowness. Retry on 403 statusCode - return new Promise((resolve, reject) => { - let pollCount = 0; - SpinniesManager.init(); - SpinniesManager.add('pollFetchProject', { - text: i18n(`${i18nKey}.pollFetchProject.checkingProject`, { - accountIdentifier: uiAccountDescription(accountId), - }), - }); - const pollInterval = setInterval(async () => { - try { - const response = await fetchProject(accountId, projectName); - if (response && response.data) { - SpinniesManager.remove('pollFetchProject'); - clearInterval(pollInterval); - resolve(response); - } - } catch (err) { - if ( - isSpecifiedError(err, { - statusCode: 403, - category: 'GATED', - subCategory: 'BuildPipelineErrorType.PORTAL_GATED', - }) && - pollCount < 15 - ) { - pollCount += 1; - } else { - SpinniesManager.remove('pollFetchProject'); - clearInterval(pollInterval); - reject(err); - } - } - }, POLLING_DELAY); - }); -}; - -const ensureProjectExists = async ( - accountId, - projectName, - { - forceCreate = false, - allowCreate = true, - noLogs = false, - withPolling = false, - uploadCommand = false, - } = {} -) => { - const accountIdentifier = uiAccountDescription(accountId); - try { - const { data: project } = withPolling - ? await pollFetchProject(accountId, projectName) - : await fetchProject(accountId, projectName); - return { projectExists: !!project, project }; - } catch (err) { - if (isSpecifiedError(err, { statusCode: 404 })) { - let shouldCreateProject = forceCreate; - if (allowCreate && !shouldCreateProject) { - const promptKey = uploadCommand ? 'createPromptUpload' : 'createPrompt'; - const promptResult = await promptUser([ - { - name: 'shouldCreateProject', - message: i18n(`${i18nKey}.ensureProjectExists.${promptKey}`, { - projectName, - accountIdentifier, - }), - type: 'confirm', - }, - ]); - shouldCreateProject = promptResult.shouldCreateProject; - } - - if (shouldCreateProject) { - try { - const { data: project } = await createProject(accountId, projectName); - logger.success( - i18n(`${i18nKey}.ensureProjectExists.createSuccess`, { - projectName, - accountIdentifier, - }) - ); - return { projectExists: true, project }; - } catch (err) { - return logError(err, new ApiErrorContext({ accountId })); - } - } else { - if (!noLogs) { - logger.log( - i18n(`${i18nKey}.ensureProjectExists.notFound`, { - projectName, - accountIdentifier, - }) - ); - } - return { projectExists: false }; - } - } - if ( - isSpecifiedError(err, { - statusCode: 401, - }) - ) { - logger.error(err.message); - process.exit(EXIT_CODES.ERROR); - } - logError(err, new ApiErrorContext({ accountId })); - process.exit(EXIT_CODES.ERROR); - } -}; - -const getProjectHomeUrl = accountId => { - const baseUrl = getHubSpotWebsiteOrigin( - getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD - ); - - return `${baseUrl}/developer-projects/${accountId}`; -}; - -const getProjectDetailUrl = (projectName, accountId) => { - if (!projectName) return; - return `${getProjectHomeUrl(accountId)}/project/${projectName}`; -}; - -const getProjectActivityUrl = (projectName, accountId) => { - if (!projectName) return; - return `${getProjectDetailUrl(projectName, accountId)}/activity`; -}; - -const getProjectBuildDetailUrl = (projectName, buildId, accountId) => { - if (!projectName || !buildId || !accountId) return; - return `${getProjectActivityUrl(projectName, accountId)}/build/${buildId}`; -}; - -const getProjectDeployDetailUrl = (projectName, deployId, accountId) => { - if (!projectName || !deployId || !accountId) return; - return `${getProjectActivityUrl(projectName, accountId)}/deploy/${deployId}`; -}; - -const uploadProjectFiles = async ( - accountId, - projectName, - filePath, - uploadMessage, - platformVersion -) => { - SpinniesManager.init({}); - const accountIdentifier = uiAccountDescription(accountId); - - SpinniesManager.add('upload', { - text: i18n(`${i18nKey}.uploadProjectFiles.add`, { - accountIdentifier, - projectName, - }), - succeedColor: 'white', - }); - - let buildId; - let error; - - try { - const { data: upload } = await uploadProject( - accountId, - projectName, - filePath, - uploadMessage, - platformVersion - ); - - buildId = upload.buildId; - - SpinniesManager.succeed('upload', { - text: i18n(`${i18nKey}.uploadProjectFiles.succeed`, { - accountIdentifier, - projectName, - }), - }); - - logger.debug( - i18n(`${i18nKey}.uploadProjectFiles.buildCreated`, { - buildId, - projectName, - }) - ); - } catch (err) { - SpinniesManager.fail('upload', { - text: i18n(`${i18nKey}.uploadProjectFiles.fail`, { - accountIdentifier, - projectName, - }), - }); - - error = err; - } - - return { buildId, error }; -}; - -const pollProjectBuildAndDeploy = async ( - accountId, - projectConfig, - tempFile, - buildId, - silenceLogs = false -) => { - let buildStatus = await pollBuildStatus( - accountId, - projectConfig.name, - buildId, - null, - silenceLogs - ); - - if (!silenceLogs) { - uiLine(); - } - - const result = { - succeeded: true, - buildId, - buildResult: buildStatus, - deployResult: null, - }; - - if (buildStatus.status === 'FAILURE') { - result.succeeded = false; - return result; - } else if (buildStatus.isAutoDeployEnabled) { - if (!silenceLogs) { - logger.log( - i18n( - `${i18nKey}.pollProjectBuildAndDeploy.buildSucceededAutomaticallyDeploying`, - { - accountIdentifier: uiAccountDescription(accountId), - buildId, - } - ) - ); - - await displayWarnLogs(accountId, projectConfig.name, buildId); - } - - // autoDeployId of 0 indicates a skipped deploy - const getIsDeploying = () => - buildStatus.autoDeployId > 0 && buildStatus.deployStatusTaskLocator; - - // Sometimes the deploys do not immediately initiate, give them a chance to kick off - if (!getIsDeploying()) { - buildStatus = await pollBuildAutodeployStatus( - accountId, - projectConfig.name, - buildId - ); - } - - if (getIsDeploying()) { - const deployStatus = await pollDeployStatus( - accountId, - projectConfig.name, - buildStatus.deployStatusTaskLocator.id, - buildId, - silenceLogs - ); - result.deployResult = deployStatus; - - if (deployStatus.status === 'FAILURE') { - result.succeeded = false; - } - } else if (!silenceLogs) { - logger.log( - i18n( - `${i18nKey}.pollProjectBuildAndDeploy.unableToFindAutodeployStatus`, - { - buildId, - viewDeploysLink: uiLink( - i18n(`${i18nKey}.pollProjectBuildAndDeploy.viewDeploys`), - getProjectActivityUrl(projectConfig.name, accountId) - ), - } - ) - ); - } - } - - try { - if (tempFile) { - tempFile.removeCallback(); - logger.debug( - i18n(`${i18nKey}.pollProjectBuildAndDeploy.cleanedUpTempFile`, { - path: tempFile.name, - }) - ); - } - } catch (e) { - logger.error(e); - } - - if (result && result.deployResult) { - await displayWarnLogs( - accountId, - projectConfig.name, - result.deployResult.deployId, - true - ); - } - return result; -}; - -const handleProjectUpload = async ( - accountId, - projectConfig, - projectDir, - callbackFunc, - uploadMessage -) => { - const srcDir = path.resolve(projectDir, projectConfig.srcDir); - - const filenames = fs.readdirSync(srcDir); - if (!filenames || filenames.length === 0) { - logger.log( - i18n(`${i18nKey}.handleProjectUpload.emptySource`, { - srcDir: projectConfig.srcDir, - }) - ); - process.exit(EXIT_CODES.SUCCESS); - } - - const tempFile = tmp.fileSync({ postfix: '.zip' }); - - logger.debug( - i18n(`${i18nKey}.handleProjectUpload.compressing`, { - path: tempFile.name, - }) - ); - - const output = fs.createWriteStream(tempFile.name); - const archive = archiver('zip'); - - const result = new Promise(resolve => - output.on('close', async function() { - let uploadResult = {}; - - logger.debug( - i18n(`${i18nKey}.handleProjectUpload.compressed`, { - byteCount: archive.pointer(), - }) - ); - - const { buildId, error } = await uploadProjectFiles( - accountId, - projectConfig.name, - tempFile.name, - uploadMessage, - projectConfig.platformVersion - ); - - if (error) { - uploadResult.uploadError = error; - } else if (callbackFunc) { - uploadResult = await callbackFunc( - accountId, - projectConfig, - tempFile, - buildId - ); - } - resolve(uploadResult || {}); - }) - ); - - archive.pipe(output); - - let loggedIgnoredNodeModule = false; - - archive.directory(srcDir, false, file => { - const ignored = shouldIgnoreFile(file.name, true); - if (ignored) { - const isNodeModule = file.name.includes('node_modules'); - - if (!isNodeModule || !loggedIgnoredNodeModule) { - logger.debug( - i18n(`${i18nKey}.handleProjectUpload.fileFiltered`, { - filename: file.name, - }) - ); - } - - if (isNodeModule && !loggedIgnoredNodeModule) { - loggedIgnoredNodeModule = true; - } - } - return ignored ? false : file; - }); - - archive.finalize(); - - return result; -}; - -const makePollTaskStatusFunc = ({ - statusFn, - structureFn, - statusText, - statusStrings, - linkToHubSpot, -}) => { - return async ( - accountId, - taskName, - taskId, - deployedBuildId = null, - silenceLogs = false - ) => { - const displayId = deployedBuildId || taskId; - - if (linkToHubSpot && !silenceLogs) { - logger.log( - `\n${linkToHubSpot(accountId, taskName, taskId, deployedBuildId)}\n` - ); - } - - SpinniesManager.init(); - - const overallTaskSpinniesKey = `overallTaskStatus-${statusText.STATUS_TEXT}`; - - SpinniesManager.add(overallTaskSpinniesKey, { - text: 'Beginning', - succeedColor: 'white', - failColor: 'white', - failPrefix: chalk.bold('!'), - }); - - const [ - { data: initialTaskStatus }, - { - data: { topLevelComponentsWithChildren: taskStructure }, - }, - ] = await Promise.all([ - statusFn(accountId, taskName, taskId), - structureFn(accountId, taskName, taskId), - ]); - - const tasksById = initialTaskStatus[statusText.SUBTASK_KEY].reduce( - (acc, task) => { - const { id, visible } = task; - if (visible) { - acc[id] = task; - } - return acc; - }, - {} - ); - - const structuredTasks = Object.keys(taskStructure).map(key => { - return { - ...tasksById[key], - subtasks: taskStructure[key] - .filter(taskId => Boolean(tasksById[taskId])) - .map(taskId => tasksById[taskId]), - }; - }); - - const numComponents = structuredTasks.length; - const componentCountText = silenceLogs - ? '' - : i18n( - numComponents === 1 - ? `${i18nKey}.makePollTaskStatusFunc.componentCountSingular` - : `${i18nKey}.makePollTaskStatusFunc.componentCount`, - { numComponents } - ) + '\n'; - - SpinniesManager.update(overallTaskSpinniesKey, { - text: `${statusStrings.INITIALIZE( - taskName, - displayId - )}\n${componentCountText}`, - }); - - if (!silenceLogs) { - const addTaskSpinner = (task, indent, newline) => { - const taskName = task[statusText.SUBTASK_NAME_KEY]; - const taskType = task[statusText.TYPE_KEY]; - const formattedTaskType = PROJECT_TASK_TYPES[taskType] - ? `[${PROJECT_TASK_TYPES[taskType]}]` - : ''; - const text = `${indent <= 2 ? statusText.STATUS_TEXT : ''} ${chalk.bold( - taskName - )} ${formattedTaskType} ...${newline ? '\n' : ''}`; - - SpinniesManager.add(task.id, { - text, - indent, - succeedColor: 'white', - failColor: 'white', - }); - }; - - structuredTasks.forEach(task => { - addTaskSpinner(task, 2, !task.subtasks || task.subtasks.length === 0); - task.subtasks.forEach((subtask, i) => - addTaskSpinner(subtask, 4, i === task.subtasks.length - 1) - ); - }); - } - - return new Promise((resolve, reject) => { - const pollInterval = setInterval(async () => { - let taskStatus; - try { - const { data } = await statusFn(accountId, taskName, taskId); - taskStatus = data; - } catch (e) { - logger.debug(e); - logError( - e, - new ApiErrorContext({ - accountId, - projectName: taskName, - }) - ); - return reject( - new Error( - i18n( - `${i18nKey}.makePollTaskStatusFunc.errorFetchingTaskStatus`, - { - taskType: - statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY - ? 'build' - : 'deploy', - } - ) - ) - ); - } - - if ( - !taskStatus || - !taskStatus.status || - !taskStatus[statusText.SUBTASK_KEY] - ) { - return reject( - new Error( - i18n( - `${i18nKey}.makePollTaskStatusFunc.errorFetchingTaskStatus`, - { - taskType: - statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY - ? 'build' - : 'deploy', - } - ) - ) - ); - } - - const { status, [statusText.SUBTASK_KEY]: subTaskStatus } = taskStatus; - - if (SpinniesManager.hasActiveSpinners()) { - subTaskStatus.forEach(subTask => { - const { id, status } = subTask; - const spinner = SpinniesManager.pick(id); - - if (!spinner || spinner.status !== SPINNER_STATUS.SPINNING) { - return; - } - - const topLevelTask = structuredTasks.find(t => t.id == id); - - if ( - status === statusText.STATES.SUCCESS || - status === statusText.STATES.FAILURE - ) { - const taskStatusText = - subTask.status === statusText.STATES.SUCCESS - ? i18n(`${i18nKey}.makePollTaskStatusFunc.successStatusText`) - : i18n(`${i18nKey}.makePollTaskStatusFunc.failedStatusText`); - const hasNewline = - spinner.text.includes('\n') || Boolean(topLevelTask); - const updatedText = `${spinner.text.replace( - '\n', - '' - )} ${taskStatusText}${hasNewline ? '\n' : ''}`; - - if (status === statusText.STATES.SUCCESS) { - SpinniesManager.succeed(id, { text: updatedText }); - } else { - SpinniesManager.fail(id, { text: updatedText }); - } - - if (topLevelTask) { - topLevelTask.subtasks.forEach(currentSubtask => - SpinniesManager.remove(currentSubtask.id) - ); - } - } - }); - - if (status === statusText.STATES.SUCCESS) { - SpinniesManager.succeed(overallTaskSpinniesKey, { - text: statusStrings.SUCCESS(taskName, displayId), - }); - clearInterval(pollInterval); - resolve(taskStatus); - } else if (status === statusText.STATES.FAILURE) { - SpinniesManager.fail(overallTaskSpinniesKey, { - text: statusStrings.FAIL(taskName, displayId), - }); - - if (!silenceLogs) { - const failedSubtasks = subTaskStatus.filter( - subtask => subtask.status === 'FAILURE' - ); - - uiLine(); - logger.log( - `${statusStrings.SUBTASK_FAIL( - displayId, - failedSubtasks.length === 1 - ? failedSubtasks[0][statusText.SUBTASK_NAME_KEY] - : failedSubtasks.length + ' components' - )}\n` - ); - logger.log('See below for a summary of errors.'); - uiLine(); - - const displayErrors = failedSubtasks.filter( - subtask => - subtask.standardError.subCategory !== - PROJECT_ERROR_TYPES.SUBBUILD_FAILED && - subtask.standardError.subCategory !== - PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED - ); - - displayErrors.forEach(subTask => { - logger.log( - `\n--- ${chalk.bold( - subTask[statusText.SUBTASK_NAME_KEY] - )} failed with the following error ---` - ); - logger.error(subTask.errorMessage); - - // Log nested errors - if (subTask.standardError && subTask.standardError.errors) { - logger.log(); - subTask.standardError.errors.forEach(error => { - logger.log(error.message); - }); - } - }); - } - clearInterval(pollInterval); - resolve(taskStatus); - } else if (!subTaskStatus.length) { - clearInterval(pollInterval); - resolve(taskStatus); - } - } - }, POLLING_DELAY); - }); - }; -}; - -const pollBuildAutodeployStatus = (accountId, taskName, buildId) => { - return new Promise((resolve, reject) => { - let maxIntervals = (30 * 1000) / POLLING_DELAY; // Num of intervals in ~30s - - const pollInterval = setInterval(async () => { - let taskStatus; - try { - taskStatus = await getBuildStatus(accountId, taskName, buildId); - } catch (e) { - logger.debug(e); - return reject( - new Error( - i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId }) - ) - ); - } - - if (!taskStatus || !taskStatus.status) { - return reject( - new Error( - i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId }) - ) - ); - } - - if (taskStatus.deployStatusTaskLocator || maxIntervals <= 0) { - clearInterval(pollInterval); - resolve(taskStatus); - } else { - maxIntervals -= 1; - } - }, POLLING_DELAY); - }); -}; - -const pollBuildStatus = makePollTaskStatusFunc({ - linkToHubSpot: (accountId, taskName, taskId) => - uiLink( - `View build #${taskId} in HubSpot`, - getProjectBuildDetailUrl(taskName, taskId, accountId) - ), - statusFn: getBuildStatus, - structureFn: getBuildStructure, - statusText: PROJECT_BUILD_TEXT, - statusStrings: { - INITIALIZE: (name, buildId) => `Building ${chalk.bold(name)} #${buildId}`, - SUCCESS: (name, buildId) => `Built ${chalk.bold(name)} #${buildId}`, - FAIL: (name, buildId) => `Failed to build ${chalk.bold(name)} #${buildId}`, - SUBTASK_FAIL: (buildId, name) => - `Build #${buildId} failed because there was a problem\nbuilding ${chalk.bold( - name - )}`, - }, -}); - -const pollDeployStatus = makePollTaskStatusFunc({ - linkToHubSpot: (accountId, taskName, taskId, deployedBuildId) => - uiLink( - `View deploy of build #${deployedBuildId} in HubSpot`, - getProjectDeployDetailUrl(taskName, taskId, accountId) - ), - statusFn: getDeployStatus, - structureFn: getDeployStructure, - statusText: PROJECT_DEPLOY_TEXT, - statusStrings: { - INITIALIZE: (name, buildId) => - `Deploying build #${buildId} in ${chalk.bold(name)}`, - SUCCESS: (name, buildId) => - `Deployed build #${buildId} in ${chalk.bold(name)}`, - FAIL: (name, buildId) => - `Failed to deploy build #${buildId} in ${chalk.bold(name)}`, - SUBTASK_FAIL: (deployedBuildId, name) => - `Deploy for build #${deployedBuildId} failed because there was a\nproblem deploying ${chalk.bold( - name - )}`, - }, -}); - -const logFeedbackMessage = buildId => { - if (buildId > 0 && buildId % FEEDBACK_INTERVAL === 0) { - uiLine(); - logger.log(i18n(`${i18nKey}.logFeedbackMessage.feedbackHeader`)); - uiLine(); - logger.log(i18n(`${i18nKey}.logFeedbackMessage.feedbackMessage`)); - } -}; - -const createProjectComponent = async ( - component, - name, - projectComponentsVersion -) => { - const i18nKey = 'commands.project.subcommands.add'; - const componentName = name; - - const configInfo = await getProjectConfig(); - - if (!configInfo.projectDir && !configInfo.projectConfig) { - logger.error(i18n(`${i18nKey}.error.locationInProject`)); - process.exit(EXIT_CODES.ERROR); - } - - const componentPath = path.join( - configInfo.projectDir, - configInfo.projectConfig.srcDir, - component.insertPath, - componentName - ); - - await downloadGithubRepoContents( - HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, - component.path, - componentPath, - projectComponentsVersion - ); -}; - -const displayWarnLogs = async ( - accountId, - projectName, - taskId, - isDeploy = false -) => { - let result; - - if (isDeploy) { - try { - const { data } = await fetchDeployWarnLogs( - accountId, - projectName, - taskId - ); - result = data; - } catch (e) { - logError(e); - } - } else { - try { - const { data } = await fetchBuildWarnLogs(accountId, projectName, taskId); - result = data; - } catch (e) { - logError(e); - } - } - - if (result && result.logs) { - const logLength = result.logs.length; - result.logs.forEach((log, i) => { - logger.warn(log.message); - if (i < logLength - 1) { - logger.log(''); - } - }); - } -}; - -const getProjectComponentsByVersion = async projectComponentsVersion => { - const config = await fetchFileFromRepository( - HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, - 'config.json', - projectComponentsVersion - ); - - return config[PROJECT_COMPONENT_TYPES.COMPONENTS]; -}; - -module.exports = { - writeProjectConfig, - getProjectConfig, - getIsInProject, - pollProjectBuildAndDeploy, - handleProjectUpload, - createProjectConfig, - validateProjectConfig, - getProjectHomeUrl, - getProjectDetailUrl, - getProjectBuildDetailUrl, - pollBuildStatus, - pollDeployStatus, - ensureProjectExists, - logFeedbackMessage, - createProjectComponent, - displayWarnLogs, - getProjectComponentsByVersion, -}; diff --git a/lib/ProjectLogsManager.ts b/lib/projects/ProjectLogsManager.ts similarity index 62% rename from lib/ProjectLogsManager.ts rename to lib/projects/ProjectLogsManager.ts index b8afc34f2..55288526d 100644 --- a/lib/ProjectLogsManager.ts +++ b/lib/projects/ProjectLogsManager.ts @@ -1,23 +1,40 @@ -// @ts-nocheck -const { getProjectConfig, ensureProjectExists } = require('./projects'); -const { - fetchProjectComponentsMetadata, -} = require('@hubspot/local-dev-lib/api/projects'); -const { i18n } = require('./lang'); -const { uiLink } = require('./ui'); +import { getProjectConfig, ensureProjectExists } from './index'; +import { fetchProjectComponentsMetadata } from '@hubspot/local-dev-lib/api/projects'; +import { AppFunctionComponentMetadata } from '@hubspot/local-dev-lib/types/ComponentStructure'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { i18n } from '../lang'; +import { uiLink } from '../ui'; const i18nKey = 'commands.project.subcommands.logs'; -class ProjectLogsManager { - reset() { - Object.keys(this).forEach(key => { - if (Object.hasOwn(this, key)) { - this[key] = undefined; - } - }); +class _ProjectLogsManager { + projectName: string | undefined; + projectId: number | undefined; + accountId: number | undefined; + functions: AppFunctionComponentMetadata[]; + selectedFunction: AppFunctionComponentMetadata | undefined; + functionName: string | undefined; + appId: number | undefined; + isPublicFunction: boolean | undefined; + endpointName: string | undefined; + + reset(): void { + this.projectName = undefined; + this.projectId = undefined; + this.accountId = undefined; + this.functions = []; + this.selectedFunction = undefined; + this.functionName = undefined; + this.appId = undefined; + this.isPublicFunction = undefined; + this.endpointName = undefined; + } + + constructor() { + this.functions = []; } - async init(accountId) { + async init(accountId: number): Promise { const { projectConfig } = await getProjectConfig(); if (!projectConfig || !projectConfig.name) { @@ -50,11 +67,16 @@ class ProjectLogsManager { await this.fetchFunctionDetails(); } - async fetchFunctionDetails() { + async fetchFunctionDetails(): Promise { if (!this.projectId) { throw new Error(i18n(`${i18nKey}.errors.noProjectConfig`)); } + if (!this.accountId) { + logger.debug(i18n(`${i18nKey}.errors.projectLogsManagerNotInitialized`)); + throw new Error(i18n(`${i18nKey}.errors.generic`)); + } + const { data: { topLevelComponentMetadata }, } = await fetchProjectComponentsMetadata(this.accountId, this.projectId); @@ -64,15 +86,12 @@ class ProjectLogsManager { return type && type.name === 'PRIVATE_APP'; }); - if (!this.functions) { - this.functions = []; - } - apps.forEach(app => { this.functions.push( - ...app.featureComponents.filter( + // If component type is APP_FUNCTION, we can safely cast as AppFunctionComponentMetadata + ...(app.featureComponents.filter( component => component.type.name === 'APP_FUNCTION' - ) + ) as AppFunctionComponentMetadata[]) ); }); @@ -89,26 +108,20 @@ class ProjectLogsManager { } getFunctionNames() { - if (!this.functions) { - return []; - } return this.functions.map( serverlessFunction => serverlessFunction.componentName ); } - setFunction(functionName) { - if (!this.functions) { + setFunction(functionName: string) { + if (!(this.functions.length > 0)) { throw new Error( i18n(`${i18nKey}.errors.noFunctionsInProject`, { link: uiLink( i18n(`${i18nKey}.errors.noFunctionsLinkText`), 'https://developers.hubspot.com/docs/platform/serverless-functions' ), - }), - { - projectName: this.projectName, - } + }) ); } @@ -133,7 +146,6 @@ class ProjectLogsManager { if (this.selectedFunction.deployOutput.endpoint) { this.endpointName = this.selectedFunction.deployOutput.endpoint.path; - this.method = this.selectedFunction.deployOutput.endpoint.method; this.isPublicFunction = true; } else { this.isPublicFunction = false; @@ -141,4 +153,4 @@ class ProjectLogsManager { } } -module.exports = new ProjectLogsManager(); +export const ProjectLogsManager = new _ProjectLogsManager(); diff --git a/lib/projects/buildAndDeploy.ts b/lib/projects/buildAndDeploy.ts new file mode 100644 index 000000000..1a1960f7f --- /dev/null +++ b/lib/projects/buildAndDeploy.ts @@ -0,0 +1,610 @@ +import chalk from 'chalk'; +import { FileResult } from 'tmp'; +import { HubSpotPromise } from '@hubspot/local-dev-lib/types/Http'; +import { ComponentStructureResponse } from '@hubspot/local-dev-lib/types/ComponentStructure'; +import { Build } from '@hubspot/local-dev-lib/types/Build'; +import { Deploy } from '@hubspot/local-dev-lib/types/Deploy'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { + getBuildStatus, + getBuildStructure, + getDeployStatus, + getDeployStructure, + fetchBuildWarnLogs, + fetchDeployWarnLogs, +} from '@hubspot/local-dev-lib/api/projects'; +import { WarnLogsResponse } from '@hubspot/local-dev-lib/types/Project'; + +import { + POLLING_DELAY, + PROJECT_BUILD_TEXT, + PROJECT_DEPLOY_TEXT, + PROJECT_TASK_TYPES, + PROJECT_ERROR_TYPES, +} from '../constants'; +import SpinniesManager from '../ui/SpinniesManager'; +import { i18n } from '../lang'; +import { logError, ApiErrorContext } from '../errorHandlers'; +import { uiLine, uiLink, uiAccountDescription } from '../ui'; +import { + getProjectBuildDetailUrl, + getProjectDeployDetailUrl, + getProjectActivityUrl, +} from './urls'; +import { + ProjectConfig, + ProjectTask, + ProjectSubtask, + ProjectPollStatusFunctionText, +} from '../../types/Projects'; + +const i18nKey = 'lib.projectBuildAndDeploy'; + +const SPINNER_STATUS = { + SPINNING: 'spinning', +}; + +function getSubtasks(task: ProjectTask): ProjectSubtask[] { + if ('subbuildStatuses' in task) { + return task.subbuildStatuses; + } + return task.subdeployStatuses; +} + +function getSubtaskName(task: ProjectSubtask): string { + if ('buildName' in task) { + return task.buildName; + } + return task.deployName; +} + +function getSubtaskType(task: ProjectSubtask): string { + if ('buildType' in task) { + return task.buildType; + } + return task.deployType; +} + +type PollTaskStatusFunctionConfig = { + statusFn: ( + accountId: number, + projectName: string, + taskId: number + ) => HubSpotPromise; + structureFn: ( + accountId: number, + projectName: string, + taskId: number + ) => HubSpotPromise; + statusText: ProjectPollStatusFunctionText; + statusStrings: PollTaskStatusStrings; + linkToHubSpot: ( + accountId: number, + taskName: string, + taskId: number, + deployedBuildId: number | null + ) => void; +}; + +type PollTaskStatus = 'INITIALIZE' | 'SUCCESS' | 'FAIL' | 'SUBTASK_FAIL'; + +type PollTaskStatusStringFunction = (name: string, taskId: number) => string; + +type PollTaskStatusStrings = { + [k in PollTaskStatus]: PollTaskStatusStringFunction; +}; + +type PollTaskStatusFunction = ( + accountId: number, + taskName: string, + taskId: number, + deployedBuildId: number | null, + silenceLogs: boolean +) => Promise; + +function makePollTaskStatusFunc({ + statusFn, + structureFn, + statusText, + statusStrings, + linkToHubSpot, +}: PollTaskStatusFunctionConfig): PollTaskStatusFunction { + return async function ( + accountId, + taskName, + taskId, + deployedBuildId = null, + silenceLogs = false + ) { + const displayId = deployedBuildId || taskId; + + if (linkToHubSpot && !silenceLogs) { + logger.log( + `\n${linkToHubSpot(accountId, taskName, taskId, deployedBuildId)}\n` + ); + } + + SpinniesManager.init(); + + const overallTaskSpinniesKey = `overallTaskStatus-${statusText.STATUS_TEXT}`; + + SpinniesManager.add(overallTaskSpinniesKey, { + text: 'Beginning', + succeedColor: 'white', + failColor: 'white', + failPrefix: chalk.bold('!'), + }); + + const [ + { data: initialTaskStatus }, + { + data: { topLevelComponentsWithChildren: taskStructure }, + }, + ] = await Promise.all([ + statusFn(accountId, taskName, taskId), + structureFn(accountId, taskName, taskId), + ]); + + const subtasks = getSubtasks(initialTaskStatus); + + const tasksById = subtasks.reduce( + (acc: { [key: string]: ProjectSubtask }, subtask) => { + const { id, visible } = subtask; + if (visible) { + acc[id] = subtask; + } + return acc; + }, + {} + ); + + const structuredTasks = Object.keys(taskStructure).map(key => { + return { + ...tasksById[key], + subtasks: taskStructure[key] + .filter(taskId => Boolean(tasksById[taskId])) + .map(taskId => tasksById[taskId]), + }; + }); + + const numComponents = structuredTasks.length; + const componentCountText = silenceLogs + ? '' + : i18n( + numComponents === 1 + ? `${i18nKey}.makePollTaskStatusFunc.componentCountSingular` + : `${i18nKey}.makePollTaskStatusFunc.componentCount`, + { numComponents } + ) + '\n'; + + SpinniesManager.update(overallTaskSpinniesKey, { + text: `${statusStrings.INITIALIZE( + taskName, + displayId + )}\n${componentCountText}`, + }); + + if (!silenceLogs) { + function addTaskSpinner( + subtask: ProjectSubtask, + indent: number, + newline: boolean + ): void { + const taskName = getSubtaskName(subtask); + const taskType = getSubtaskType(subtask); + const formattedTaskType = PROJECT_TASK_TYPES[taskType] + ? `[${PROJECT_TASK_TYPES[taskType]}]` + : ''; + const text = `${indent <= 2 ? statusText.STATUS_TEXT : ''} ${chalk.bold( + taskName + )} ${formattedTaskType} ...${newline ? '\n' : ''}`; + + SpinniesManager.add(subtask.id, { + text, + indent, + succeedColor: 'white', + failColor: 'white', + }); + } + + structuredTasks.forEach(task => { + addTaskSpinner(task, 2, !task.subtasks || task.subtasks.length === 0); + task.subtasks.forEach((subtask, i) => + addTaskSpinner(subtask, 4, i === task.subtasks.length - 1) + ); + }); + } + + return new Promise((resolve, reject) => { + const pollInterval = setInterval(async () => { + let taskStatus: T; + try { + const { data } = await statusFn(accountId, taskName, taskId); + taskStatus = data; + } catch (e) { + logger.debug(e); + logError( + e, + new ApiErrorContext({ + accountId, + projectName: taskName, + }) + ); + return reject( + new Error( + i18n( + `${i18nKey}.makePollTaskStatusFunc.errorFetchingTaskStatus`, + { + taskType: + statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY + ? 'build' + : 'deploy', + } + ) + ) + ); + } + + const subtasks = getSubtasks(taskStatus); + + if (!taskStatus || !taskStatus.status || !subtasks) { + return reject( + new Error( + i18n( + `${i18nKey}.makePollTaskStatusFunc.errorFetchingTaskStatus`, + { + taskType: + statusText.TYPE_KEY === PROJECT_BUILD_TEXT.TYPE_KEY + ? 'build' + : 'deploy', + } + ) + ) + ); + } + + const { status } = taskStatus; + + if (SpinniesManager.hasActiveSpinners()) { + subtasks.forEach(subtask => { + const { id, status } = subtask; + const spinner = SpinniesManager.pick(id); + + if (!spinner || spinner.status !== SPINNER_STATUS.SPINNING) { + return; + } + + const topLevelTask = structuredTasks.find(t => t.id == id); + + if ( + status === statusText.STATES.SUCCESS || + status === statusText.STATES.FAILURE + ) { + const taskStatusText = + subtask.status === statusText.STATES.SUCCESS + ? i18n(`${i18nKey}.makePollTaskStatusFunc.successStatusText`) + : i18n(`${i18nKey}.makePollTaskStatusFunc.failedStatusText`); + const hasNewline = + spinner?.text?.includes('\n') || Boolean(topLevelTask); + const updatedText = `${spinner?.text?.replace( + '\n', + '' + )} ${taskStatusText}${hasNewline ? '\n' : ''}`; + + if (status === statusText.STATES.SUCCESS) { + SpinniesManager.succeed(id, { text: updatedText }); + } else { + SpinniesManager.fail(id, { text: updatedText }); + } + + if (topLevelTask) { + topLevelTask.subtasks.forEach(currentSubtask => + SpinniesManager.remove(currentSubtask.id) + ); + } + } + }); + + if (status === statusText.STATES.SUCCESS) { + SpinniesManager.succeed(overallTaskSpinniesKey, { + text: statusStrings.SUCCESS(taskName, displayId), + }); + clearInterval(pollInterval); + resolve(taskStatus); + } else if (status === statusText.STATES.FAILURE) { + SpinniesManager.fail(overallTaskSpinniesKey, { + text: statusStrings.FAIL(taskName, displayId), + }); + + if (!silenceLogs) { + const failedSubtasks = subtasks.filter( + subtask => subtask.status === 'FAILURE' + ); + + uiLine(); + logger.log( + `${statusStrings.SUBTASK_FAIL( + failedSubtasks.length === 1 + ? getSubtaskName(failedSubtasks[0]) + : failedSubtasks.length + ' components', + displayId + )}\n` + ); + logger.log('See below for a summary of errors.'); + uiLine(); + + const displayErrors = failedSubtasks.filter( + subtask => + subtask?.standardError?.subCategory !== + PROJECT_ERROR_TYPES.SUBBUILD_FAILED && + subtask?.standardError?.subCategory !== + PROJECT_ERROR_TYPES.SUBDEPLOY_FAILED + ); + + displayErrors.forEach(subTask => { + logger.log( + `\n--- ${chalk.bold( + getSubtaskName(subTask) + )} failed with the following error ---` + ); + logger.error(subTask.errorMessage); + + // Log nested errors + if (subTask.standardError && subTask.standardError.errors) { + logger.log(); + subTask.standardError.errors.forEach(error => { + logger.log(error.message); + }); + } + }); + } + clearInterval(pollInterval); + resolve(taskStatus); + } else if (!subtasks.length) { + clearInterval(pollInterval); + resolve(taskStatus); + } + } + }, POLLING_DELAY); + }); + }; +} + +function pollBuildAutodeployStatus( + accountId: number, + taskName: string, + buildId: number +): Promise { + return new Promise((resolve, reject) => { + let maxIntervals = (30 * 1000) / POLLING_DELAY; // Num of intervals in ~30s + + const pollInterval = setInterval(async () => { + let build: Build; + try { + const response = await getBuildStatus(accountId, taskName, buildId); + build = response.data; + } catch (e) { + logger.debug(e); + return reject( + new Error( + i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId }) + ) + ); + } + + if (!build || !build.status) { + return reject( + new Error( + i18n(`${i18nKey}.pollBuildAutodeployStatusError`, { buildId }) + ) + ); + } + + if (build.deployStatusTaskLocator || maxIntervals <= 0) { + clearInterval(pollInterval); + resolve(build); + } else { + maxIntervals -= 1; + } + }, POLLING_DELAY); + }); +} + +export const pollBuildStatus = makePollTaskStatusFunc({ + linkToHubSpot: (accountId, taskName, taskId) => + uiLink( + `View build #${taskId} in HubSpot`, + getProjectBuildDetailUrl(taskName, taskId, accountId) + ), + statusFn: getBuildStatus, + structureFn: getBuildStructure, + statusText: PROJECT_BUILD_TEXT, + statusStrings: { + INITIALIZE: (name, buildId) => `Building ${chalk.bold(name)} #${buildId}`, + SUCCESS: (name, buildId) => `Built ${chalk.bold(name)} #${buildId}`, + FAIL: (name, buildId) => `Failed to build ${chalk.bold(name)} #${buildId}`, + SUBTASK_FAIL: (buildId, name) => + `Build #${buildId} failed because there was a problem\nbuilding ${chalk.bold( + name + )}`, + }, +}); + +export const pollDeployStatus = makePollTaskStatusFunc({ + linkToHubSpot: (accountId, taskName, taskId, deployedBuildId) => + uiLink( + `View deploy of build #${deployedBuildId} in HubSpot`, + getProjectDeployDetailUrl(taskName, taskId, accountId) + ), + statusFn: getDeployStatus, + structureFn: getDeployStructure, + statusText: PROJECT_DEPLOY_TEXT, + statusStrings: { + INITIALIZE: (name, buildId) => + `Deploying build #${buildId} in ${chalk.bold(name)}`, + SUCCESS: (name, buildId) => + `Deployed build #${buildId} in ${chalk.bold(name)}`, + FAIL: (name, buildId) => + `Failed to deploy build #${buildId} in ${chalk.bold(name)}`, + SUBTASK_FAIL: (deployedBuildId, name) => + `Deploy for build #${deployedBuildId} failed because there was a\nproblem deploying ${chalk.bold( + name + )}`, + }, +}); + +type ProjectPollResult = { + succeeded: boolean; + buildId: number; + buildResult: Build; + deployResult: Deploy | null; +}; + +export async function displayWarnLogs( + accountId: number, + projectName: string, + taskId: number, + isDeploy = false +): Promise { + let result: WarnLogsResponse | undefined; + + if (isDeploy) { + try { + const { data } = await fetchDeployWarnLogs( + accountId, + projectName, + taskId + ); + result = data; + } catch (e) { + logError(e); + } + } else { + try { + const { data } = await fetchBuildWarnLogs(accountId, projectName, taskId); + result = data; + } catch (e) { + logError(e); + } + } + + if (result && result.logs) { + const logLength = result.logs.length; + result.logs.forEach((log, i) => { + logger.warn(log.message); + if (i < logLength - 1) { + logger.log(''); + } + }); + } +} + +export async function pollProjectBuildAndDeploy( + accountId: number, + projectConfig: ProjectConfig, + tempFile: FileResult, + buildId: number, + silenceLogs = false +): Promise { + let buildStatus = await pollBuildStatus( + accountId, + projectConfig.name, + buildId, + null, + silenceLogs + ); + + if (!silenceLogs) { + uiLine(); + } + + const result: ProjectPollResult = { + succeeded: true, + buildId, + buildResult: buildStatus, + deployResult: null, + }; + + if (buildStatus.status === 'FAILURE') { + result.succeeded = false; + return result; + } else if (buildStatus.isAutoDeployEnabled) { + if (!silenceLogs) { + logger.log( + i18n( + `${i18nKey}.pollProjectBuildAndDeploy.buildSucceededAutomaticallyDeploying`, + { + accountIdentifier: uiAccountDescription(accountId), + buildId, + } + ) + ); + + await displayWarnLogs(accountId, projectConfig.name, buildId); + } + + // autoDeployId of 0 indicates a skipped deploy + const getIsDeploying = () => + buildStatus.autoDeployId > 0 && buildStatus.deployStatusTaskLocator; + + // Sometimes the deploys do not immediately initiate, give them a chance to kick off + if (!getIsDeploying()) { + buildStatus = await pollBuildAutodeployStatus( + accountId, + projectConfig.name, + buildId + ); + } + + if (getIsDeploying()) { + const deployStatus = await pollDeployStatus( + accountId, + projectConfig.name, + Number(buildStatus.deployStatusTaskLocator.id), + buildId, + silenceLogs + ); + result.deployResult = deployStatus; + + if (deployStatus.status === 'FAILURE') { + result.succeeded = false; + } + } else if (!silenceLogs) { + logger.log( + i18n( + `${i18nKey}.pollProjectBuildAndDeploy.unableToFindAutodeployStatus`, + { + buildId, + viewDeploysLink: uiLink( + i18n(`${i18nKey}.pollProjectBuildAndDeploy.viewDeploys`), + getProjectActivityUrl(projectConfig.name, accountId) + ), + } + ) + ); + } + } + + try { + if (tempFile) { + tempFile.removeCallback(); + logger.debug( + i18n(`${i18nKey}.pollProjectBuildAndDeploy.cleanedUpTempFile`, { + path: tempFile.name, + }) + ); + } + } catch (e) { + logger.error(e); + } + + if (result && result.deployResult) { + await displayWarnLogs( + accountId, + projectConfig.name, + result.deployResult.deployId, + true + ); + } + return result; +} diff --git a/lib/projects/index.ts b/lib/projects/index.ts new file mode 100644 index 000000000..9a86d6b1e --- /dev/null +++ b/lib/projects/index.ts @@ -0,0 +1,372 @@ +import fs from 'fs-extra'; +import path from 'path'; +import findup from 'findup-sync'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { fetchFileFromRepository } from '@hubspot/local-dev-lib/github'; +import { + createProject, + fetchProject, +} from '@hubspot/local-dev-lib/api/projects'; +import { isSpecifiedError } from '@hubspot/local-dev-lib/errors/index'; +import { getCwd, getAbsoluteFilePath } from '@hubspot/local-dev-lib/path'; +import { downloadGithubRepoContents } from '@hubspot/local-dev-lib/github'; +import { RepoPath } from '@hubspot/local-dev-lib/types/Github'; +import { Project } from '@hubspot/local-dev-lib/types/Project'; +import { HubSpotPromise } from '@hubspot/local-dev-lib/types/Http'; + +import { + FEEDBACK_INTERVAL, + POLLING_DELAY, + PROJECT_CONFIG_FILE, + HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + PROJECT_COMPONENT_TYPES, +} from '../constants'; +import { promptUser } from '../prompts/promptUtils'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { uiLine, uiAccountDescription, uiCommandReference } from '../ui'; +import { i18n } from '../lang'; +import SpinniesManager from '../ui/SpinniesManager'; +import { + ProjectTemplate, + ProjectConfig, + ProjectAddComponentData, + ProjectTemplateRepoConfig, + ComponentTemplate, +} from '../../types/Projects'; +import { logError, ApiErrorContext } from '../errorHandlers/index'; + +const i18nKey = 'lib.projects'; + +export function writeProjectConfig( + configPath: string, + config: ProjectConfig +): boolean { + try { + fs.ensureFileSync(configPath); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + logger.debug(`Wrote project config at ${configPath}`); + } catch (e) { + logger.debug(e); + return false; + } + return true; +} + +export function getIsInProject(dir?: string): boolean { + const configPath = getProjectConfigPath(dir); + return !!configPath; +} + +function getProjectConfigPath(dir?: string): string | null { + const projectDir = dir ? getAbsoluteFilePath(dir) : getCwd(); + + const configPath = findup(PROJECT_CONFIG_FILE, { + cwd: projectDir, + nocase: true, + }); + + return configPath; +} + +export async function getProjectConfig(dir?: string): Promise<{ + projectDir: string | null; + projectConfig: ProjectConfig | null; +}> { + const configPath = await getProjectConfigPath(dir); + if (!configPath) { + return { projectConfig: null, projectDir: null }; + } + + try { + const config = fs.readFileSync(configPath); + const projectConfig: ProjectConfig = JSON.parse(config.toString()); + return { + projectDir: path.dirname(configPath), + projectConfig, + }; + } catch (e) { + logger.error('Could not read from project config'); + return { projectConfig: null, projectDir: null }; + } +} + +export async function createProjectConfig( + projectPath: string, + projectName: string, + template: ProjectTemplate, + templateSource: RepoPath, + githubRef: string +): Promise { + const { projectConfig, projectDir } = await getProjectConfig(projectPath); + + if (projectConfig) { + logger.warn( + projectPath === projectDir + ? 'A project already exists in that location.' + : `Found an existing project definition in ${projectDir}.` + ); + + const { shouldContinue } = await promptUser<{ shouldContinue: boolean }>([ + { + name: 'shouldContinue', + message: () => { + return projectPath === projectDir + ? 'Do you want to overwrite the existing project definition with a new one?' + : `Continue creating a new project in ${projectPath}?`; + }, + type: 'confirm', + default: false, + }, + ]); + + if (!shouldContinue) { + return false; + } + } + + const projectConfigPath = path.join(projectPath, PROJECT_CONFIG_FILE); + + logger.log( + `Creating project config in ${ + projectPath ? projectPath : 'the current folder' + }` + ); + + const hasCustomTemplateSource = Boolean(templateSource); + + await downloadGithubRepoContents( + templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + template.path, + projectPath, + hasCustomTemplateSource ? undefined : githubRef + ); + const _config: ProjectConfig = JSON.parse( + fs.readFileSync(projectConfigPath).toString() + ); + writeProjectConfig(projectConfigPath, { + ..._config, + name: projectName, + }); + + if (template.name === 'no-template') { + fs.ensureDirSync(path.join(projectPath, 'src')); + } + + return true; +} + +export function validateProjectConfig( + projectConfig: ProjectConfig, + projectDir: string +): void { + if (!projectConfig) { + logger.error( + i18n(`${i18nKey}.validateProjectConfig.configNotFound`, { + createCommand: uiCommandReference('hs project create'), + }) + ); + return process.exit(EXIT_CODES.ERROR); + } + + if (!projectConfig.name || !projectConfig.srcDir) { + logger.error(i18n(`${i18nKey}.validateProjectConfig.configMissingFields`)); + return process.exit(EXIT_CODES.ERROR); + } + + const resolvedPath = path.resolve(projectDir, projectConfig.srcDir); + if (!resolvedPath.startsWith(projectDir)) { + const projectConfigFile = path.relative( + '.', + path.join(projectDir, PROJECT_CONFIG_FILE) + ); + logger.error( + i18n(`${i18nKey}.validateProjectConfig.srcOutsideProjectDir`, { + srcDir: projectConfig.srcDir, + projectConfig: projectConfigFile, + }) + ); + return process.exit(EXIT_CODES.ERROR); + } + + if (!fs.existsSync(resolvedPath)) { + logger.error( + i18n(`${i18nKey}.validateProjectConfig.srcDirNotFound`, { + srcDir: projectConfig.srcDir, + projectDir: projectDir, + }) + ); + + return process.exit(EXIT_CODES.ERROR); + } +} + +async function pollFetchProject( + accountId: number, + projectName: string +): HubSpotPromise { + // Temporary solution for gating slowness. Retry on 403 statusCode + return new Promise((resolve, reject) => { + let pollCount = 0; + SpinniesManager.init(); + SpinniesManager.add('pollFetchProject', { + text: i18n(`${i18nKey}.pollFetchProject.checkingProject`, { + accountIdentifier: uiAccountDescription(accountId), + }), + }); + const pollInterval = setInterval(async () => { + try { + const response = await fetchProject(accountId, projectName); + if (response && response.data) { + SpinniesManager.remove('pollFetchProject'); + clearInterval(pollInterval); + resolve(response); + } + } catch (err) { + if ( + isSpecifiedError(err, { + statusCode: 403, + category: 'GATED', + subCategory: 'BuildPipelineErrorType.PORTAL_GATED', + }) && + pollCount < 15 + ) { + pollCount += 1; + } else { + SpinniesManager.remove('pollFetchProject'); + clearInterval(pollInterval); + reject(err); + } + } + }, POLLING_DELAY); + }); +} + +export async function ensureProjectExists( + accountId: number, + projectName: string, + { + forceCreate = false, + allowCreate = true, + noLogs = false, + withPolling = false, + uploadCommand = false, + } = {} +): Promise<{ + projectExists: boolean; + project?: Project; +}> { + const accountIdentifier = uiAccountDescription(accountId); + try { + const { data: project } = withPolling + ? await pollFetchProject(accountId, projectName) + : await fetchProject(accountId, projectName); + return { projectExists: !!project, project }; + } catch (err) { + if (isSpecifiedError(err, { statusCode: 404 })) { + let shouldCreateProject = forceCreate; + if (allowCreate && !shouldCreateProject) { + const promptKey = uploadCommand ? 'createPromptUpload' : 'createPrompt'; + const promptResult = await promptUser<{ shouldCreateProject: boolean }>( + [ + { + name: 'shouldCreateProject', + message: i18n(`${i18nKey}.ensureProjectExists.${promptKey}`, { + projectName, + accountIdentifier, + }), + type: 'confirm', + }, + ] + ); + shouldCreateProject = promptResult.shouldCreateProject; + } + + if (shouldCreateProject) { + try { + const { data: project } = await createProject(accountId, projectName); + logger.success( + i18n(`${i18nKey}.ensureProjectExists.createSuccess`, { + projectName, + accountIdentifier, + }) + ); + return { projectExists: true, project }; + } catch (err) { + logError(err, new ApiErrorContext({ accountId })); + return { projectExists: false }; + } + } else { + if (!noLogs) { + logger.log( + i18n(`${i18nKey}.ensureProjectExists.notFound`, { + projectName, + accountIdentifier, + }) + ); + } + return { projectExists: false }; + } + } + if ( + isSpecifiedError(err, { + statusCode: 401, + }) + ) { + logger.error(err.message); + process.exit(EXIT_CODES.ERROR); + } + logError(err, new ApiErrorContext({ accountId })); + process.exit(EXIT_CODES.ERROR); + } +} + +export function logFeedbackMessage(buildId: number): void { + if (buildId > 0 && buildId % FEEDBACK_INTERVAL === 0) { + uiLine(); + logger.log(i18n(`${i18nKey}.logFeedbackMessage.feedbackHeader`)); + uiLine(); + logger.log(i18n(`${i18nKey}.logFeedbackMessage.feedbackMessage`)); + } +} + +export async function createProjectComponent( + component: ProjectAddComponentData, + name: string, + projectComponentsVersion: string +): Promise { + const i18nKey = 'commands.project.subcommands.add'; + const componentName = name; + + const configInfo = await getProjectConfig(); + + if (!configInfo.projectDir || !configInfo.projectConfig) { + logger.error(i18n(`${i18nKey}.error.locationInProject`)); + process.exit(EXIT_CODES.ERROR); + } + + const componentPath = path.join( + configInfo.projectDir, + configInfo.projectConfig.srcDir, + component.insertPath, + componentName + ); + + await downloadGithubRepoContents( + HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + component.path, + componentPath, + projectComponentsVersion + ); +} + +export async function getProjectComponentsByVersion( + projectComponentsVersion: string +): Promise { + const config = await fetchFileFromRepository( + HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, + 'config.json', + projectComponentsVersion + ); + + return config[PROJECT_COMPONENT_TYPES.COMPONENTS] || []; +} diff --git a/lib/projects/structure.ts b/lib/projects/structure.ts new file mode 100644 index 000000000..a36b10480 --- /dev/null +++ b/lib/projects/structure.ts @@ -0,0 +1,217 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { ValueOf } from '@hubspot/local-dev-lib/types/Utils'; +import { walk } from '@hubspot/local-dev-lib/fs'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { logError } from '../errorHandlers/index'; + +export type Component = { + type: ComponentTypes; + config: object; + runnable: boolean; + path: string; +}; + +type PrivateAppComponentConfig = { + name: string; + description: string; + uid: string; + scopes: Array; + public: boolean; + extensions?: { + crm: { + cards: Array<{ file: string }>; + }; + }; +}; + +type PublicAppComponentConfig = { + name: string; + uid: string; + description: string; + allowedUrls: Array; + auth: { + redirectUrls: Array; + requiredScopes: Array; + optionalScopes: Array; + conditionallyRequiredScopes: Array; + }; + support: { + supportEmail: string; + documentationUrl: string; + supportUrl: string; + supportPhone: string; + }; + extensions?: { + crm: { + cards: Array<{ file: string }>; + }; + }; + webhooks?: { + file: string; + }; +}; + +type AppCardComponentConfig = { + type: 'crm-card'; + data: { + title: string; + uid: string; + location: string; + module: { + file: string; + }; + objectTypes: Array<{ name: string }>; + }; +}; + +type GenericComponentConfig = + | PublicAppComponentConfig + | PrivateAppComponentConfig + | AppCardComponentConfig; + +export const COMPONENT_TYPES = { + privateApp: 'private-app', + publicApp: 'public-app', + hublTheme: 'hubl-theme', +} as const; + +type ComponentTypes = ValueOf; + +export const CONFIG_FILES: { + [k in ValueOf]: string; +} = { + [COMPONENT_TYPES.privateApp]: 'app.json', + [COMPONENT_TYPES.publicApp]: 'public-app.json', + [COMPONENT_TYPES.hublTheme]: 'theme.json', +}; + +function getComponentTypeFromConfigFile( + configFile: string +): ComponentTypes | null { + let key: ComponentTypes; + for (key in CONFIG_FILES) { + if (CONFIG_FILES[key] === configFile) { + return key; + } + } + return null; +} + +function loadConfigFile(configPath: string): GenericComponentConfig | null { + if (configPath) { + try { + const source = fs.readFileSync(configPath); + const parsedConfig = JSON.parse(source.toString()); + return parsedConfig; + } catch (e) { + logger.debug(e); + } + } + return null; +} + +export function getAppCardConfigs( + appConfig: PublicAppComponentConfig | PrivateAppComponentConfig, + appPath: string +): Array { + const cardConfigs: Array = []; + let cards; + + if (appConfig && appConfig.extensions && appConfig.extensions.crm) { + cards = appConfig.extensions.crm.cards; + } + + if (cards) { + cards.forEach(({ file }: { file?: string }) => { + if (typeof file === 'string') { + const cardConfigPath = path.join(appPath, file); + const cardConfig = loadConfigFile(cardConfigPath); + + if (cardConfig && 'type' in cardConfig) { + cardConfigs.push(cardConfig); + } + } + }); + } + + return cardConfigs; +} + +function getIsLegacyApp( + appConfig: GenericComponentConfig, + appPath: string +): boolean { + let hasAnyReactExtensions = false; + + if (appConfig && 'extensions' in appConfig) { + const cardConfigs = getAppCardConfigs(appConfig, appPath); + + if (!cardConfigs.length) { + // Assume any app that does not have any cards is not legacy + return false; + } + + cardConfigs.forEach(cardConfig => { + if (!hasAnyReactExtensions) { + const isReactExtension = + cardConfig && + !!cardConfig.data && + !!cardConfig.data.module && + !!cardConfig.data.module.file; + + hasAnyReactExtensions = isReactExtension; + } + }); + } + + return !hasAnyReactExtensions; +} + +export async function findProjectComponents( + projectSourceDir: string +): Promise> { + const components: Array = []; + let projectFiles: Array = []; + + try { + projectFiles = await walk(projectSourceDir); + } catch (e) { + logError(e); + } + + projectFiles.forEach(projectFile => { + // Find app components + const { base, dir } = path.parse(projectFile); + + if (Object.values(CONFIG_FILES).includes(base)) { + const parsedConfig = loadConfigFile(projectFile); + + if (parsedConfig) { + const isLegacy = getIsLegacyApp(parsedConfig, dir); + const isHublTheme = base === CONFIG_FILES[COMPONENT_TYPES.hublTheme]; + const componentType = getComponentTypeFromConfigFile(base); + + if (componentType) { + components.push({ + type: componentType, + config: parsedConfig, + runnable: !isLegacy && !isHublTheme, + path: dir, + }); + } + } + } + }); + + return components; +} + +export function getProjectComponentTypes(components: Array): { + [key in ComponentTypes]?: boolean; +} { + const projectContents: { [key in ComponentTypes]?: boolean } = {}; + + components.forEach(({ type }) => (projectContents[type] = true)); + return projectContents; +} diff --git a/lib/projects/upload.ts b/lib/projects/upload.ts new file mode 100644 index 000000000..98068259d --- /dev/null +++ b/lib/projects/upload.ts @@ -0,0 +1,179 @@ +import archiver from 'archiver'; +import tmp, { FileResult } from 'tmp'; +import fs from 'fs-extra'; +import path from 'path'; +import { uploadProject } from '@hubspot/local-dev-lib/api/projects'; +import { shouldIgnoreFile } from '@hubspot/local-dev-lib/ignoreRules'; +import { logger } from '@hubspot/local-dev-lib/logger'; + +import SpinniesManager from '../ui/SpinniesManager'; +import { uiAccountDescription } from '../ui'; +import { i18n } from '../lang'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { ProjectConfig } from '../../types/Projects'; + +const i18nKey = 'lib.projectUpload'; + +async function uploadProjectFiles( + accountId: number, + projectName: string, + filePath: string, + uploadMessage: string, + platformVersion: string +): Promise<{ buildId?: number; error: unknown }> { + SpinniesManager.init({}); + const accountIdentifier = uiAccountDescription(accountId); + + SpinniesManager.add('upload', { + text: i18n(`${i18nKey}.uploadProjectFiles.add`, { + accountIdentifier, + projectName, + }), + succeedColor: 'white', + }); + + let buildId: number | undefined; + let error: unknown; + + try { + const { data: upload } = await uploadProject( + accountId, + projectName, + filePath, + uploadMessage, + platformVersion + ); + + buildId = upload.buildId; + + SpinniesManager.succeed('upload', { + text: i18n(`${i18nKey}.uploadProjectFiles.succeed`, { + accountIdentifier, + projectName, + }), + }); + + if (buildId) { + logger.debug( + i18n(`${i18nKey}.uploadProjectFiles.buildCreated`, { + buildId, + projectName, + }) + ); + } + } catch (err) { + SpinniesManager.fail('upload', { + text: i18n(`${i18nKey}.uploadProjectFiles.fail`, { + accountIdentifier, + projectName, + }), + }); + + error = err; + } + + return { buildId, error }; +} + +type ProjectUploadCallbackFunction = ( + accountId: number, + projectConfig: ProjectConfig, + tempFile: FileResult, + buildId?: number +) => Promise; + +type ProjectUploadDefaultResult = { + uploadError?: unknown; +}; + +export async function handleProjectUpload( + accountId: number, + projectConfig: ProjectConfig, + projectDir: string, + callbackFunc: ProjectUploadCallbackFunction, + uploadMessage: string +) { + const srcDir = path.resolve(projectDir, projectConfig.srcDir); + + const filenames = fs.readdirSync(srcDir); + if (!filenames || filenames.length === 0) { + logger.log( + i18n(`${i18nKey}.handleProjectUpload.emptySource`, { + srcDir: projectConfig.srcDir, + }) + ); + process.exit(EXIT_CODES.SUCCESS); + } + + const tempFile = tmp.fileSync({ postfix: '.zip' }); + + logger.debug( + i18n(`${i18nKey}.handleProjectUpload.compressing`, { + path: tempFile.name, + }) + ); + + const output = fs.createWriteStream(tempFile.name); + const archive = archiver('zip'); + + const result = new Promise(resolve => + output.on('close', async function () { + let uploadResult: ProjectUploadDefaultResult | T | undefined; + + logger.debug( + i18n(`${i18nKey}.handleProjectUpload.compressed`, { + byteCount: archive.pointer(), + }) + ); + + const { buildId, error } = await uploadProjectFiles( + accountId, + projectConfig.name, + tempFile.name, + uploadMessage, + projectConfig.platformVersion + ); + + if (error) { + console.log(error); + uploadResult = { uploadError: error }; + } else if (callbackFunc) { + uploadResult = await callbackFunc( + accountId, + projectConfig, + tempFile, + buildId + ); + } + resolve(uploadResult || {}); + }) + ); + + archive.pipe(output); + + let loggedIgnoredNodeModule = false; + + archive.directory(srcDir, false, file => { + const ignored = shouldIgnoreFile(file.name, true); + if (ignored) { + const isNodeModule = file.name.includes('node_modules'); + + if (!isNodeModule || !loggedIgnoredNodeModule) { + logger.debug( + i18n(`${i18nKey}.handleProjectUpload.fileFiltered`, { + filename: file.name, + }) + ); + } + + if (isNodeModule && !loggedIgnoredNodeModule) { + loggedIgnoredNodeModule = true; + } + } + return ignored ? false : file; + }); + + archive.finalize(); + + return result; +} diff --git a/lib/projects/urls.ts b/lib/projects/urls.ts new file mode 100644 index 000000000..55e33bd42 --- /dev/null +++ b/lib/projects/urls.ts @@ -0,0 +1,42 @@ +import { getHubSpotWebsiteOrigin } from '@hubspot/local-dev-lib/urls'; +import { getEnv } from '@hubspot/local-dev-lib/config'; +import { ENVIRONMENTS } from '@hubspot/local-dev-lib/constants/environments'; + +function getProjectHomeUrl(accountId: number): string { + const baseUrl = getHubSpotWebsiteOrigin( + getEnv(accountId) === 'qa' ? ENVIRONMENTS.QA : ENVIRONMENTS.PROD + ); + + return `${baseUrl}/developer-projects/${accountId}`; +} + +export function getProjectDetailUrl( + projectName: string, + accountId: number +): string | undefined { + if (!projectName) return; + return `${getProjectHomeUrl(accountId)}/project/${projectName}`; +} + +export function getProjectActivityUrl( + projectName: string, + accountId: number +): string { + return `${getProjectDetailUrl(projectName, accountId)}/activity`; +} + +export function getProjectBuildDetailUrl( + projectName: string, + buildId: number, + accountId: number +): string { + return `${getProjectActivityUrl(projectName, accountId)}/build/${buildId}`; +} + +export function getProjectDeployDetailUrl( + projectName: string, + deployId: number, + accountId: number +): string { + return `${getProjectActivityUrl(projectName, accountId)}/deploy/${deployId}`; +} diff --git a/lib/projectsWatch.ts b/lib/projects/watch.ts similarity index 97% rename from lib/projectsWatch.ts rename to lib/projects/watch.ts index 95ea171b1..a67104526 100644 --- a/lib/projectsWatch.ts +++ b/lib/projects/watch.ts @@ -3,8 +3,8 @@ const chokidar = require('chokidar'); const path = require('path'); const chalk = require('chalk'); const { default: PQueue } = require('p-queue'); -const { logError, ApiErrorContext } = require('./errorHandlers/index'); -const { i18n } = require('./lang'); +const { logError, ApiErrorContext } = require('../errorHandlers/index'); +const { i18n } = require('../lang'); const { logger } = require('@hubspot/local-dev-lib/logger'); const { isAllowedExtension } = require('@hubspot/local-dev-lib/path'); const { shouldIgnoreFile } = require('@hubspot/local-dev-lib/ignoreRules'); @@ -16,7 +16,7 @@ const { queueBuild, } = require('@hubspot/local-dev-lib/api/projects'); const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); -const { PROJECT_ERROR_TYPES } = require('./constants'); +const { PROJECT_ERROR_TYPES } = require('../constants'); const i18nKey = 'commands.project.subcommands.watch'; diff --git a/lib/prompts/accountNamePrompt.ts b/lib/prompts/accountNamePrompt.ts index 4b1a1932c..4ca2bf86b 100644 --- a/lib/prompts/accountNamePrompt.ts +++ b/lib/prompts/accountNamePrompt.ts @@ -1,36 +1,53 @@ -// @ts-nocheck -const { accountNameExistsInConfig } = require('@hubspot/local-dev-lib/config'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { - HUBSPOT_ACCOUNT_TYPES, -} = require('@hubspot/local-dev-lib/constants/config'); +import { accountNameExistsInConfig } from '@hubspot/local-dev-lib/config'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { PromptConfig } from '../../types/Prompts'; +import { HUBSPOT_ACCOUNT_TYPES } from '@hubspot/local-dev-lib/constants/config'; +import { AccountType } from '@hubspot/local-dev-lib/types/Accounts'; const i18nKey = 'lib.prompts.accountNamePrompt'; -const getCliAccountNamePromptConfig = defaultName => ({ - name: 'name', - message: i18n(`${i18nKey}.enterAccountName`), - default: defaultName, - validate(val) { - if (typeof val !== 'string') { - return i18n(`${i18nKey}.errors.invalidName`); - } else if (!val.length) { - return i18n(`${i18nKey}.errors.nameRequired`); - } else if (val.indexOf(' ') >= 0) { - return i18n(`${i18nKey}.errors.spacesInName`); - } - return accountNameExistsInConfig(val) - ? i18n(`${i18nKey}.errors.accountNameExists`, { name: val }) - : true; - }, -}); - -const cliAccountNamePrompt = defaultName => { - return promptUser(getCliAccountNamePromptConfig(defaultName)); +type AccountNamePromptResponse = { + name: string; }; -const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => { +export function getCliAccountNamePromptConfig( + defaultName?: string +): PromptConfig { + return { + name: 'name', + message: i18n(`${i18nKey}.enterAccountName`), + default: defaultName, + validate(val?: string) { + if (typeof val !== 'string') { + return i18n(`${i18nKey}.errors.invalidName`); + } else if (!val.length) { + return i18n(`${i18nKey}.errors.nameRequired`); + } else if (val.indexOf(' ') >= 0) { + return i18n(`${i18nKey}.errors.spacesInName`); + } + return accountNameExistsInConfig(val) + ? i18n(`${i18nKey}.errors.accountNameExists`, { name: val }) + : true; + }, + }; +} + +export function cliAccountNamePrompt( + defaultName: string +): Promise { + return promptUser( + getCliAccountNamePromptConfig(defaultName) + ); +} + +export function hubspotAccountNamePrompt({ + accountType, + currentPortalCount = 0, +}: { + accountType: AccountType; + currentPortalCount?: number; +}): Promise { const isDevelopmentSandbox = accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX; const isSandbox = @@ -39,8 +56,8 @@ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => { const isDeveloperTestAccount = accountType === HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST; - let promptMessageString; - let defaultName; + let promptMessageString: string | undefined; + let defaultName: string | undefined; if (isSandbox) { promptMessageString = isDevelopmentSandbox ? i18n(`${i18nKey}.enterDevelopmentSandboxName`) @@ -52,11 +69,11 @@ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => { }); } - return promptUser([ + return promptUser([ { name: 'name', message: promptMessageString, - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalidName`); } else if (!val.trim().length) { @@ -69,10 +86,4 @@ const hubspotAccountNamePrompt = ({ accountType, currentPortalCount = 0 }) => { default: defaultName, }, ]); -}; - -module.exports = { - getCliAccountNamePromptConfig, - cliAccountNamePrompt, - hubspotAccountNamePrompt, -}; +} diff --git a/lib/prompts/accountsPrompt.ts b/lib/prompts/accountsPrompt.ts index 779dc80c8..bee5ce10a 100644 --- a/lib/prompts/accountsPrompt.ts +++ b/lib/prompts/accountsPrompt.ts @@ -1,32 +1,41 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); -const { uiAccountDescription } = require('../ui'); +import { + getConfigDefaultAccount, + getConfigAccounts, +} 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'; +import { PromptChoices } from '../../types/Prompts'; -const mapAccountChoices = portals => - portals.map(p => ({ - name: uiAccountDescription(p.portalId, false), - value: p.name || p.portalId, - })); +function mapAccountChoices( + portals: CLIAccount[] | null | undefined +): PromptChoices { + return ( + portals?.map(p => ({ + name: uiAccountDescription(getAccountIdentifier(p), false), + value: String(p.name || getAccountIdentifier(p)), + })) || [] + ); +} -const i18nKey = 'commands.accounts.subcommands.use'; +const i18nKey = 'commands.account.subcommands.use'; -const selectAccountFromConfig = async (config, prompt) => { - const { default: selectedDefault } = await promptUser([ +export async function selectAccountFromConfig(prompt = ''): Promise { + const accountsList = getConfigAccounts(); + const defaultAccount = getConfigDefaultAccount(); + + const { default: selectedDefault } = await promptUser<{ default: string }>([ { type: 'list', - look: false, name: 'default', pageSize: 20, message: prompt || i18n(`${i18nKey}.promptMessage`), - choices: mapAccountChoices(config.portals), - default: config.defaultPortal, + choices: mapAccountChoices(accountsList), + default: defaultAccount ?? undefined, }, ]); return selectedDefault; -}; - -module.exports = { - selectAccountFromConfig, -}; +} diff --git a/lib/prompts/cleanUploadPrompt.ts b/lib/prompts/cleanUploadPrompt.ts deleted file mode 100644 index 5e6caa039..000000000 --- a/lib/prompts/cleanUploadPrompt.ts +++ /dev/null @@ -1,21 +0,0 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); - -const i18nKey = 'lib.prompts.cleanUploadPrompt'; - -const cleanUploadPrompt = async (accountId, filePath) => { - const promptAnswer = await promptUser([ - { - name: 'cleanUpload', - message: i18n(`${i18nKey}.message`, { accountId, filePath }), - type: 'confirm', - default: false, - }, - ]); - return promptAnswer.cleanUpload; -}; - -module.exports = { - cleanUploadPrompt, -}; diff --git a/lib/prompts/cmsFieldPrompt.ts b/lib/prompts/cmsFieldPrompt.ts index 2d88b244e..3f6ecbde4 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,21 +22,19 @@ 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 promptVal = await promptUser([ + const selection = fileChoices.map(fileChoice => ({ + name: fileChoice.replace(projectDirRegex, ''), + value: fileChoice, + })); + + const promptVal = await promptUser<{ filePathChoice: string }>([ { message: i18n(`${i18nKey}.fieldsPrompt`, { dir: fileDir }), type: 'list', @@ -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/prompts/createApiSamplePrompt.ts b/lib/prompts/createApiSamplePrompt.ts index 7a45a4f43..c8d78b6ef 100644 --- a/lib/prompts/createApiSamplePrompt.ts +++ b/lib/prompts/createApiSamplePrompt.ts @@ -1,54 +1,91 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { 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`)); - }); - }, -}); - -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`)); - }); - }, -}); - -const createApiSamplePrompt = async samplesConfig => { +type SampleChoice = { + name: string; + description: string; + id: string; + languages: string[]; +}; + +type SampleConfig = { + samples: SampleChoice[]; +}; + +type SampleTypePromptResponse = { + sampleType?: string; +}; + +type LanguagePromptResponse = { + sampleLanguage?: string; +}; + +type CreateApiSamplePromptResponse = SampleTypePromptResponse & + LanguagePromptResponse; + +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) { + return new Promise(function (resolve, reject) { + if (input && input.length > 0) { + resolve(true); + } else { + reject(i18n(`${i18nKey}.errors.apiSampleAppRequired`)); + } + }); + }, + }; +} + +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) { + 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 sampleTypeAnswer = await promptUser( + getSampleTypesPrompt(samples) + ); const chosenSample = samples.find( sample => sample.id === sampleTypeAnswer.sampleType ); - const { languages } = chosenSample; - const languagesAnswer = await promptUser(getLanguagesPrompt(languages)); + const { languages } = chosenSample!; + const languagesAnswer = await promptUser( + getLanguagesPrompt(languages) + ); return { ...sampleTypeAnswer, ...languagesAnswer, @@ -56,8 +93,4 @@ const createApiSamplePrompt = async samplesConfig => { } catch (e) { return {}; } -}; - -module.exports = { - createApiSamplePrompt, -}; +} diff --git a/lib/prompts/createFunctionPrompt.ts b/lib/prompts/createFunctionPrompt.ts index 8a614bbe6..0ec6f1f46 100644 --- a/lib/prompts/createFunctionPrompt.ts +++ b/lib/prompts/createFunctionPrompt.ts @@ -1,13 +1,20 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { PromptConfig } from '../../types/Prompts'; const i18nKey = 'lib.prompts.createFunctionPrompt'; -const FUNCTIONS_FOLDER_PROMPT = { +type CreateFunctionPromptResponse = { + functionsFolder: string; + filename: string; + endpointMethod: string; + endpointPath: string; +}; + +const FUNCTIONS_FOLDER_PROMPT: PromptConfig = { name: 'functionsFolder', message: i18n(`${i18nKey}.enterFolder`), - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalid`); } else if (!val.length) { @@ -19,10 +26,10 @@ const FUNCTIONS_FOLDER_PROMPT = { }, }; -const FUNCTION_FILENAME_PROMPT = { +const FUNCTION_FILENAME_PROMPT: PromptConfig = { name: 'filename', message: i18n(`${i18nKey}.enterFilename`), - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalid`); } else if (!val.length) { @@ -34,7 +41,7 @@ const FUNCTION_FILENAME_PROMPT = { }, }; -const ENDPOINT_METHOD_PROMPT = { +const ENDPOINT_METHOD_PROMPT: PromptConfig = { type: 'list', name: 'endpointMethod', message: i18n(`${i18nKey}.selectEndpointMethod`), @@ -42,10 +49,10 @@ const ENDPOINT_METHOD_PROMPT = { choices: ['DELETE', 'GET', 'PATCH', 'POST', 'PUT'], }; -const ENDPOINT_PATH_PROMPT = { +const ENDPOINT_PATH_PROMPT: PromptConfig = { name: 'endpointPath', message: i18n(`${i18nKey}.enterEndpointPath`), - validate(val) { + validate(val?: string) { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalid`); } else if (!val.length) { @@ -57,15 +64,11 @@ const ENDPOINT_PATH_PROMPT = { }, }; -function createFunctionPrompt() { - return promptUser([ +export function createFunctionPrompt(): Promise { + return promptUser([ FUNCTIONS_FOLDER_PROMPT, FUNCTION_FILENAME_PROMPT, ENDPOINT_METHOD_PROMPT, ENDPOINT_PATH_PROMPT, ]); } - -module.exports = { - createFunctionPrompt, -}; diff --git a/lib/prompts/createModulePrompt.ts b/lib/prompts/createModulePrompt.ts index 7806b31c0..bd4e1cf3d 100644 --- a/lib/prompts/createModulePrompt.ts +++ b/lib/prompts/createModulePrompt.ts @@ -1,13 +1,22 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { PromptConfig } from '../../types/Prompts'; + +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.createModulePrompt'; -const MODULE_LABEL_PROMPT = { +type CreateModulePromptResponse = { + moduleLabel: string; + reactType: boolean; + contentTypes: string[]; + global: boolean; + availableForNewContent: boolean; +}; + +const MODULE_LABEL_PROMPT: PromptConfig = { name: 'moduleLabel', message: i18n(`${i18nKey}.enterLabel`), - validate(val) { + validate(val?: string): boolean | string { if (typeof val !== 'string') { return i18n(`${i18nKey}.errors.invalidLabel`); } else if (!val.length) { @@ -17,26 +26,34 @@ const MODULE_LABEL_PROMPT = { }, }; -const REACT_TYPE_PROMPT = { +const REACT_TYPE_PROMPT: PromptConfig = { type: 'confirm', name: 'reactType', message: i18n(`${i18nKey}.selectReactType`), default: false, }; -const CONTENT_TYPES_PROMPT = { +const CONTENT_TYPES_PROMPT: PromptConfig = { type: 'checkbox', name: 'contentTypes', message: i18n(`${i18nKey}.selectContentType`), - default: ['PAGE'], + default: ['ANY'], choices: [ - { name: 'Page', value: 'PAGE' }, + { name: 'Any', value: 'ANY' }, + { name: 'Landing page', value: 'LANDING_PAGE' }, + { name: 'Site page', value: 'SITE_PAGE' }, { name: 'Blog post', value: 'BLOG_POST' }, { name: 'Blog listing', value: 'BLOG_LISTING' }, { name: 'Email', value: 'EMAIL' }, + { name: 'Knowledge base', value: 'KNOWLEDGE_BASE' }, + { name: 'Quote template', value: 'QUOTE_TEMPLATE' }, + { name: 'Customer portal', value: 'CUSTOMER_PORTAL' }, + { name: 'Web interactive', value: 'WEB_INTERACTIVE' }, + { name: 'Subscription', value: 'SUBSCRIPTION' }, + { name: 'Membership', value: 'MEMBERSHIP' }, ], - validate: input => { - return new Promise(function(resolve, reject) { + validate: (input: string[]) => { + return new Promise(function (resolve, reject) { if (input.length > 0) { resolve(true); } @@ -45,22 +62,26 @@ const CONTENT_TYPES_PROMPT = { }, }; -const GLOBAL_PROMPT = { +const GLOBAL_PROMPT: PromptConfig = { type: 'confirm', name: 'global', message: i18n(`${i18nKey}.confirmGlobal`), default: false, }; -function createModulePrompt() { - return promptUser([ +const AVAILABLE_FOR_NEW_CONTENT: PromptConfig = { + type: 'confirm', + name: 'availableForNewContent', + message: i18n(`${i18nKey}.availableForNewContent`), + default: true, +}; + +export function createModulePrompt(): Promise { + return promptUser([ MODULE_LABEL_PROMPT, REACT_TYPE_PROMPT, CONTENT_TYPES_PROMPT, GLOBAL_PROMPT, + AVAILABLE_FOR_NEW_CONTENT, ]); } - -module.exports = { - createModulePrompt, -}; diff --git a/lib/prompts/createProjectPrompt.ts b/lib/prompts/createProjectPrompt.ts index 20d4575c2..dfae0cad5 100644 --- a/lib/prompts/createProjectPrompt.ts +++ b/lib/prompts/createProjectPrompt.ts @@ -1,42 +1,56 @@ -// @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 { RepoPath } from '@hubspot/local-dev-lib/types/Github'; +import { fetchFileFromRepository } from '@hubspot/local-dev-lib/github'; +import { logger } from '@hubspot/local-dev-lib/logger'; + +import { + PROJECT_COMPONENT_TYPES, HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, DEFAULT_PROJECT_TEMPLATE_BRANCH, -} = require('../constants'); +} from '../constants'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { + ProjectTemplate, + ProjectTemplateRepoConfig, +} from '../../types/Projects'; const i18nKey = 'lib.prompts.createProjectPrompt'; -const PROJECT_PROPERTIES = ['name', 'label', 'path', 'insertPath']; +const PROJECT_TEMPLATE_PROPERTIES = ['name', 'label', 'path', 'insertPath']; + +type CreateProjectPromptResponse = { + name: string; + dest: string; + template: ProjectTemplate; +}; -const hasAllProperties = projectList => { +function hasAllProperties(projectList: ProjectTemplate[]): boolean { return projectList.every(config => - PROJECT_PROPERTIES.every(p => + PROJECT_TEMPLATE_PROPERTIES.every(p => Object.prototype.hasOwnProperty.call(config, p) ) ); -}; +} -const createTemplateOptions = async (templateSource, githubRef) => { +async function createTemplateOptions( + templateSource: RepoPath, + githubRef: string +): Promise { const hasCustomTemplateSource = Boolean(templateSource); const branch = hasCustomTemplateSource ? DEFAULT_PROJECT_TEMPLATE_BRANCH : githubRef; - const config = await fetchFileFromRepository( + const config = await fetchFileFromRepository( templateSource || HUBSPOT_PROJECT_COMPONENTS_GITHUB_PATH, 'config.json', branch @@ -47,26 +61,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: ProjectTemplate[], + templateNameOrLabel: string +): ProjectTemplate | 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: RepoPath; + }, skipTemplatePrompt = false -) => { - let projectTemplates = []; +): Promise { + let projectTemplates: ProjectTemplate[] = []; let selectedTemplate; if (!skipTemplatePrompt) { @@ -80,12 +102,12 @@ const createProjectPrompt = async ( findTemplate(projectTemplates, promptOptions.template); } - const result = await promptUser([ + const result = await promptUser([ { name: 'name', message: i18n(`${i18nKey}.enterName`), when: !promptOptions.name, - validate: input => { + validate: (input?: string) => { if (!input) { return i18n(`${i18nKey}.errors.nameRequired`); } @@ -93,21 +115,21 @@ const createProjectPrompt = async ( }, }, { - name: 'location', - message: i18n(`${i18nKey}.enterLocation`), - when: !promptOptions.location, + name: 'dest', + message: i18n(`${i18nKey}.enterDest`), + when: !promptOptions.dest, default: answers => { const projectName = sanitizeFileName( answers.name || promptOptions.name ); return path.resolve(getCwd(), projectName); }, - validate: input => { + validate: (input?: string) => { if (!input) { - return i18n(`${i18nKey}.errors.locationRequired`); + return i18n(`${i18nKey}.errors.destRequired`); } if (fs.existsSync(input)) { - return i18n(`${i18nKey}.errors.invalidLocation`); + return i18n(`${i18nKey}.errors.invalidDest`); } if (!isValidPath(input)) { return i18n(`${i18nKey}.errors.invalidCharacters`); @@ -144,8 +166,4 @@ const createProjectPrompt = async ( } return result; -}; - -module.exports = { - createProjectPrompt, -}; +} diff --git a/lib/prompts/createTemplatePrompt.ts b/lib/prompts/createTemplatePrompt.ts index ca0cf3c19..7aa0b1a41 100644 --- a/lib/prompts/createTemplatePrompt.ts +++ b/lib/prompts/createTemplatePrompt.ts @@ -1,29 +1,31 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { PromptChoices, PromptConfig } from '../../types/Prompts'; const i18nKey = 'lib.prompts.createTemplatePrompt'; -const TEMPLATE_TYPE_PROMPT = { +const templateTypeChoices = [ + { name: 'page', value: 'page-template' }, + { name: 'email', value: 'email-template' }, + { name: 'partial', value: 'partial' }, + { name: 'global partial', value: 'global-partial' }, + { name: 'blog listing', value: 'blog-listing-template' }, + { name: 'blog post', value: 'blog-post-template' }, + { name: 'search results', value: 'search-template' }, +] satisfies PromptChoices; + +interface CreateTemplatePromptResponse { + templateType: (typeof templateTypeChoices)[number]['value']; +} + +const TEMPLATE_TYPE_PROMPT: PromptConfig = { type: 'list', name: 'templateType', message: i18n(`${i18nKey}.selectTemplate`), default: 'page', - choices: [ - { name: 'page', value: 'page-template' }, - { name: 'email', value: 'email-template' }, - { name: 'partial', value: 'partial' }, - { name: 'global partial', value: 'global-partial' }, - { name: 'blog listing', value: 'blog-listing-template' }, - { name: 'blog post', value: 'blog-post-template' }, - { name: 'search results', value: 'search-template' }, - ], + choices: templateTypeChoices, }; -function createTemplatePrompt() { - return promptUser([TEMPLATE_TYPE_PROMPT]); +export function createTemplatePrompt(): Promise { + return promptUser([TEMPLATE_TYPE_PROMPT]); } - -module.exports = { - createTemplatePrompt, -}; diff --git a/lib/prompts/downloadProjectPrompt.ts b/lib/prompts/downloadProjectPrompt.ts index 3b4852f64..68de65706 100644 --- a/lib/prompts/downloadProjectPrompt.ts +++ b/lib/prompts/downloadProjectPrompt.ts @@ -1,28 +1,43 @@ -// @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 { 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'; const i18nKey = 'lib.prompts.downloadProjectPrompt'; -const createProjectsList = async accountId => { +type DownloadProjectPromptResponse = { + project: string; +}; + +async function createProjectsList( + accountId: number | null +): Promise { try { - const { data: projects } = await fetchProjects(accountId); - return projects.results; + if (accountId) { + const { data: projects } = await fetchProjects(accountId); + return projects.results; + } + logger.error(i18n(`${i18nKey}.errors.accountIdRequired`)); + process.exit(EXIT_CODES.ERROR); } catch (e) { - logError(e, new ApiErrorContext({ accountId })); + logError(e, accountId ? new ApiErrorContext({ accountId }) : undefined); process.exit(EXIT_CODES.ERROR); } -}; +} -const downloadProjectPrompt = async (promptOptions = {}) => { +export async function downloadProjectPrompt(promptOptions: { + account?: string; + project?: string; + name?: string; +}): Promise { const accountId = getAccountId(promptOptions.account); const projectsList = await createProjectsList(accountId); - return promptUser([ + return promptUser([ { name: 'project', message: () => { @@ -30,7 +45,7 @@ const downloadProjectPrompt = async (promptOptions = {}) => { !projectsList.find(p => p.name === promptOptions.name) ? i18n(`${i18nKey}.errors.projectNotFound`, { projectName: promptOptions.project, - accountId, + accountId: accountId || '', }) : i18n(`${i18nKey}.selectProject`); }, @@ -46,8 +61,4 @@ const downloadProjectPrompt = async (promptOptions = {}) => { }), }, ]); -}; - -module.exports = { - downloadProjectPrompt, -}; +} diff --git a/lib/prompts/installPublicAppPrompt.ts b/lib/prompts/installPublicAppPrompt.ts index 243302ac6..56f6759af 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`)); @@ -23,7 +22,9 @@ const installPublicAppPrompt = async ( logger.log(i18n(`${i18nKey}.explanation`)); } - const { shouldOpenBrowser } = await promptUser({ + const { shouldOpenBrowser } = await promptUser<{ + shouldOpenBrowser: boolean; + }>({ name: 'shouldOpenBrowser', type: 'confirm', message: i18n( @@ -47,6 +48,4 @@ const installPublicAppPrompt = async ( `&redirect_uri=${encodeURIComponent(redirectUrls[0])}`; open(url); -}; - -module.exports = { installPublicAppPrompt }; +} diff --git a/lib/prompts/personalAccessKeyPrompt.ts b/lib/prompts/personalAccessKeyPrompt.ts index ed835325a..dd15dc6ef 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 PersonalAccessKeyBrowserOpenPrepResponse = { + 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') { @@ -29,9 +60,10 @@ const personalAccessKeyPrompt = async ({ env, account } = {}) => { if (account) { url = `${websiteOrigin}/personal-access-key/${account}`; } - const { personalAcessKeyBrowserOpenPrep: shouldOpen } = await promptUser([ - PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP, - ]); + const { personalAcessKeyBrowserOpenPrep: shouldOpen } = + await promptUser([ + PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP, + ]); if (shouldOpen) { open(url, { url: true }); } else { @@ -41,30 +73,31 @@ const personalAccessKeyPrompt = async ({ env, account } = {}) => { } logger.log(i18n(`${i18nKey}.logs.openingWebBrowser`, { url })); - const { personalAccessKey } = await promptUser(PERSONAL_ACCESS_KEY); + const { personalAccessKey } = + await promptUser(PERSONAL_ACCESS_KEY); return { personalAccessKey, env, }; -}; +} -const ACCOUNT_ID = { +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 = { +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 +107,10 @@ const CLIENT_ID = { }, }; -const CLIENT_SECRET = { +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 +122,17 @@ const CLIENT_SECRET = { }, }; -const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP = { - name: 'personalAcessKeyBrowserOpenPrep', - type: 'confirm', - message: i18n(`${i18nKey}.personalAccessKeyBrowserOpenPrompt`), -}; +const PERSONAL_ACCESS_KEY_BROWSER_OPEN_PREP: PromptConfig = + { + name: 'personalAcessKeyBrowserOpenPrep', + type: 'confirm', + message: i18n(`${i18nKey}.personalAccessKeyBrowserOpenPrompt`), + }; -const PERSONAL_ACCESS_KEY = { +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 +140,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 +150,18 @@ const PERSONAL_ACCESS_KEY = { }, }; -const SCOPES = { +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 = [ +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..00981368e 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 = {}) => { - return promptUser([ +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,10 +43,12 @@ const previewPrompt = (promptOptions = {}) => { }, }, ]); -}; +} -const previewProjectPrompt = async themeComponents => { - return promptUser([ +export async function previewProjectPrompt( + themeComponents: { path: string }[] +): Promise { + return promptUser([ { name: 'themeComponentPath', message: i18n(`${i18nKey}.themeProjectSelect`), @@ -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..77def953a 100644 --- a/lib/prompts/projectAddPrompt.ts +++ b/lib/prompts/projectAddPrompt.ts @@ -1,11 +1,19 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { ProjectAddComponentData } from '../../types/Projects'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.projectAddPrompt'; -const projectAddPrompt = async (components, promptOptions = {}) => { - return promptUser([ +type ProjectAddPromptResponse = { + component: ProjectAddComponentData; + name: string; +}; + +export async function projectAddPrompt( + components: ProjectAddComponentData[], + promptOptions: { name?: string; type?: string } = {} +): Promise { + return promptUser([ { name: 'component', message: () => { @@ -31,7 +39,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 +47,4 @@ const projectAddPrompt = async (components, promptOptions = {}) => { }, }, ]); -}; - -module.exports = { - projectAddPrompt, -}; +} diff --git a/lib/prompts/projectDevTargetAccountPrompt.ts b/lib/prompts/projectDevTargetAccountPrompt.ts index 07423dab0..8be678aa2 100644 --- a/lib/prompts/projectDevTargetAccountPrompt.ts +++ b/lib/prompts/projectDevTargetAccountPrompt.ts @@ -1,48 +1,72 @@ -// @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 { 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, + FetchDeveloperTestAccountsResponse, +} from '@hubspot/local-dev-lib/types/developerTestAccounts'; +import { PromptChoices } from '../../types/Prompts'; +import { EXIT_CODES } from '../enums/exitCodes'; const i18nKey = 'lib.prompts.projectDevTargetAccountPrompt'; -const mapNestedAccount = accountConfig => ({ - name: uiAccountDescription(accountConfig.portalId, 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; + } else { + logger.error(`${i18nKey}.noAccountId`); + process.exit(EXIT_CODES.ERROR); + } } catch (err) { logger.debug('Unable to fetch sandbox usage limits: ', err); } @@ -52,7 +76,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) { @@ -96,22 +120,27 @@ 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; + let devTestAccountsResponse: FetchDeveloperTestAccountsResponse | undefined; try { - const { data } = await fetchDeveloperTestAccounts(defaultAccountId); - devTestAccountsResponse = data; + 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); } - let disabledMessage = false; + let disabledMessage: string | boolean = false; if ( devTestAccountsResponse && devTestAccountsResponse.results.length >= @@ -123,9 +152,9 @@ const selectDeveloperTestTargetAccountPrompt = async ( }); } - const devTestAccounts = []; + const devTestAccounts: PromptChoices = []; if (devTestAccountsResponse && devTestAccountsResponse.results) { - const accountIds = accounts.map(account => account.portalId); + const accountIds = accounts.map(account => getAccountIdentifier(account)); devTestAccountsResponse.results.forEach(acct => { const inConfig = accountIds.includes(acct.id); @@ -133,8 +162,8 @@ const selectDeveloperTestTargetAccountPrompt = async ( name: getNonConfigDeveloperTestAccountName(acct), value: { targetAccountId: acct.id, - createdNestedAccount: false, - parentAccountId: defaultAccountId, + createNestedAccount: false, + parentAccountId: defaultAccountId ?? null, notInConfigAccount: inConfig ? null : acct, }, }); @@ -158,19 +187,22 @@ const selectDeveloperTestTargetAccountPrompt = async ( HUBSPOT_ACCOUNT_TYPE_STRINGS[HUBSPOT_ACCOUNT_TYPES.DEVELOPER_TEST], choices ); -}; - -const selectTargetAccountPrompt = async ( - defaultAccountId, - accountType, - choices -) => { - const { targetAccountInfo } = await promptUser([ +} + +async function selectTargetAccountPrompt( + defaultAccountId: number | null, + accountType: string, + choices: PromptChoices +): Promise { + const accountId = defaultAccountId; + const { targetAccountInfo } = await promptUser<{ + targetAccountInfo: CLIAccount | DeveloperTestAccount; + }>([ { name: 'targetAccountInfo', type: 'list', message: i18n(`${i18nKey}.promptMessage`, { - accountIdentifier: uiAccountDescription(defaultAccountId), + accountIdentifier: uiAccountDescription(accountId), accountType, }), choices, @@ -178,10 +210,15 @@ const selectTargetAccountPrompt = async ( ]); return targetAccountInfo; -}; - -const confirmDefaultAccountPrompt = async (accountName, accountType) => { - const { useDefaultAccount } = await promptUser([ +} + +export async function confirmDefaultAccountPrompt( + accountName: string, + accountType: AccountType +): Promise { + const { useDefaultAccount } = await promptUser<{ + useDefaultAccount: boolean; + }>([ { name: 'useDefaultAccount', type: 'confirm', @@ -192,10 +229,14 @@ const confirmDefaultAccountPrompt = async (accountName, accountType) => { }, ]); return useDefaultAccount; -}; - -const confirmUseExistingDeveloperTestAccountPrompt = async account => { - const { confirmUseExistingDeveloperTestAccount } = await promptUser([ +} + +export async function confirmUseExistingDeveloperTestAccountPrompt( + account: DeveloperTestAccount +): Promise { + const { confirmUseExistingDeveloperTestAccount } = await promptUser<{ + confirmUseExistingDeveloperTestAccount: boolean; + }>([ { name: 'confirmUseExistingDeveloperTestAccount', type: 'confirm', @@ -205,11 +246,4 @@ const confirmUseExistingDeveloperTestAccountPrompt = async account => { }, ]); return confirmUseExistingDeveloperTestAccount; -}; - -module.exports = { - selectSandboxTargetAccountPrompt, - selectDeveloperTestTargetAccountPrompt, - confirmDefaultAccountPrompt, - confirmUseExistingDeveloperTestAccountPrompt, -}; +} diff --git a/lib/prompts/projectsLogsPrompt.ts b/lib/prompts/projectsLogsPrompt.ts index 9c40bd27a..f43d69f63 100644 --- a/lib/prompts/projectsLogsPrompt.ts +++ b/lib/prompts/projectsLogsPrompt.ts @@ -1,22 +1,31 @@ -// @ts-nocheck -const { i18n } = require('../lang'); -const { promptUser } = require('./promptUtils'); +import { i18n } from '../lang'; +import { promptUser } from './promptUtils'; const i18nKey = 'lib.prompts.projectLogsPrompt'; -const projectLogsPrompt = async ({ +type ProjectLogsPromptOptions = { + functionChoices?: string[]; + promptOptions?: { function?: string }; + projectName?: string; +}; + +type ProjectLogsPromptResponse = { + functionName?: string; +}; + +export async function projectLogsPrompt({ functionChoices, promptOptions, - projectName = {}, -}) => { + projectName = '', +}: ProjectLogsPromptOptions): Promise { if (!functionChoices) { return {}; } - if (functionChoices && functionChoices.length === 1) { + if (functionChoices.length === 1) { return { functionName: functionChoices[0] }; } - return promptUser([ + return promptUser([ { name: 'functionName', type: 'list', @@ -27,8 +36,4 @@ const projectLogsPrompt = async ({ choices: functionChoices, }, ]); -}; - -module.exports = { - projectLogsPrompt, -}; +} diff --git a/lib/prompts/promptUtils.ts b/lib/prompts/promptUtils.ts index 90ce9704c..c1b1d10c8 100644 --- a/lib/prompts/promptUtils.ts +++ b/lib/prompts/promptUtils.ts @@ -2,36 +2,58 @@ const inquirer = require('inquirer'); // NOTE: we can eventually delete this and directly use inquirer.prompt when the files support imports // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const promptUser: any = inquirer.createPromptModule(); + +import { + PromptConfig, + GenericPromptResponse, + PromptWhen, + PromptChoices, +} from '../../types/Prompts'; + +const promptModule = inquirer.createPromptModule(); + +export function promptUser( + config: PromptConfig | PromptConfig[] +): Promise { + return promptModule(config); +} + +type ConfirmPromptResponse = { + choice: boolean; +}; export async function confirmPrompt( message: string, - options: { defaultAnswer?: boolean; when?: boolean | (() => boolean) } = {} + options: { defaultAnswer?: boolean; when?: PromptWhen } = {} ): Promise { - const { defaultAnswer, when } = options; - const { choice } = await promptUser([ + const { defaultAnswer = true, when } = options; + const { choice } = await promptUser([ { name: 'choice', type: 'confirm', message, - default: defaultAnswer || true, + default: defaultAnswer, when, }, ]); return choice; } +type ListPromptResponse = { + choice: string; +}; + export async function listPrompt( message: string, { choices, when, }: { - choices: Array<{ name: string; value: string }>; - when?: boolean | (() => boolean); + choices: PromptChoices; + when?: PromptWhen; } ): Promise { - const { choice } = await promptUser([ + const { choice } = await promptUser([ { name: 'choice', type: 'list', @@ -42,3 +64,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; +} diff --git a/lib/prompts/sandboxesPrompt.ts b/lib/prompts/sandboxesPrompt.ts index e77f12bde..c7a9421be 100644 --- a/lib/prompts/sandboxesPrompt.ts +++ b/lib/prompts/sandboxesPrompt.ts @@ -1,41 +1,58 @@ -// @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 { isSandbox } = require('../accountTypes'); +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, +} from '@hubspot/local-dev-lib/config'; +import { CLIAccount } from '@hubspot/local-dev-lib/types/Accounts'; +import { PromptChoices } from '../../types/Prompts'; const i18nKey = 'lib.prompts.sandboxesPrompt'; -const mapSandboxAccountChoices = portals => - portals - .filter(p => isSandbox(p)) - .map(p => { - return { - name: uiAccountDescription(p.portalId, false), - value: p.name || p.portalId, - }; - }); +type SandboxTypePromptResponse = { + type: string; +}; + +type DeleteSandboxPromptResponse = { + account: 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 { - name: `${p.name} (${p.portalId})`, - value: p.name || p.portalId, - }; - }); +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`), @@ -49,16 +66,19 @@ const sandboxTypePrompt = () => { default: HUBSPOT_ACCOUNT_TYPES.DEVELOPMENT_SANDBOX, }, ]); -}; +} -const deleteSandboxPrompt = (config, promptParentAccount = false) => { +export function deleteSandboxPrompt( + promptParentAccount = false +): Promise | void { + const accountsList = getConfigAccounts(); const choices = promptParentAccount - ? mapNonSandboxAccountChoices(config.portals) - : mapSandboxAccountChoices(config.portals); + ? mapNonSandboxAccountChoices(accountsList) + : mapSandboxAccountChoices(accountsList); if (!choices.length) { - return undefined; + return; } - return promptUser([ + return promptUser([ { name: 'account', message: i18n( @@ -67,15 +87,9 @@ const deleteSandboxPrompt = (config, promptParentAccount = false) => { : `${i18nKey}.selectAccountName` ), type: 'list', - look: false, pageSize: 20, choices, - default: config.defaultPortal, + default: getConfigDefaultAccount(), }, ]); -}; - -module.exports = { - sandboxTypePrompt, - deleteSandboxPrompt, -}; +} diff --git a/lib/prompts/secretPrompt.ts b/lib/prompts/secretPrompt.ts index ac7e62337..a2604b086 100644 --- a/lib/prompts/secretPrompt.ts +++ b/lib/prompts/secretPrompt.ts @@ -1,26 +1,51 @@ -// @ts-nocheck -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.secretPrompt'; -const SECRET_VALUE_PROMPT = { - name: 'secretValue', - type: 'password', - mask: '*', - message: i18n(`${i18nKey}.enterValue`), - validate(val) { - if (typeof val !== 'string') { - return i18n(`${i18nKey}.errors.invalidValue`); - } - return true; - }, +type SecretValuePromptResponse = { + secretValue: string; }; -function secretValuePrompt() { - return promptUser([SECRET_VALUE_PROMPT]); +export function secretValuePrompt(): Promise { + return promptUser([ + { + name: 'secretValue', + type: 'password', + mask: '*', + message: i18n(`${i18nKey}.enterValue`), + }, + ]); } -module.exports = { - secretValuePrompt, +type SecretNamePromptResponse = { + secretName: string; }; + +export function secretNamePrompt(): Promise { + return promptUser([ + { + name: 'secretName', + type: 'input', + message: i18n(`${i18nKey}.enterName`), + }, + ]); +} + +type SecretListPromptResponse = { + secretToModify: string; +}; + +export function secretListPrompt( + secrets: string[], + message: string +): Promise { + return promptUser([ + { + name: 'secretToModify', + type: 'list', + choices: secrets, + message, + }, + ]); +} diff --git a/lib/prompts/selectHubDBTablePrompt.ts b/lib/prompts/selectHubDBTablePrompt.ts new file mode 100644 index 000000000..d443ed1ba --- /dev/null +++ b/lib/prompts/selectHubDBTablePrompt.ts @@ -0,0 +1,81 @@ +import fs from 'fs'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; +import { debugError } from '../errorHandlers/index'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { fetchTables } from '@hubspot/local-dev-lib/api/hubdb'; +import { EXIT_CODES } from '../enums/exitCodes'; +import { Table } from '@hubspot/local-dev-lib/types/Hubdb'; +import { isValidPath, untildify } from '@hubspot/local-dev-lib/path'; + +const i18nKey = 'lib.prompts.selectHubDBTablePrompt'; + +async function fetchHubDBOptions(accountId: number) { + try { + const { + data: { results: tables }, + } = await fetchTables(accountId); + if (tables.length === 0) { + logger.log(i18n(`${i18nKey}.errors.noTables`, { accountId })); + process.exit(EXIT_CODES.SUCCESS); + } + return tables; + } catch (error) { + debugError(error, { accountId }); + logger.error(i18n(`${i18nKey}.errors.errorFetchingTables`, { accountId })); + process.exit(EXIT_CODES.ERROR); + } +} + +export async function selectHubDBTablePrompt({ + accountId, + options, + skipDestPrompt = true, +}: { + accountId: number; + options: { + tableId?: number; + dest?: string; + }; + skipDestPrompt?: boolean; +}) { + const hubdbTables: Table[] = (await fetchHubDBOptions(accountId)) || []; + const id = options.tableId?.toString(); + const isValidTable = + options.tableId && hubdbTables.find(table => table.id === id); + + return promptUser([ + { + name: 'tableId', + message: i18n(`${i18nKey}.selectTable`), + when: !id && !isValidTable, + type: 'list', + choices: hubdbTables.map(table => { + return { + name: `${table.label} (${table.id})`, + value: table.id, + }; + }), + }, + { + name: 'dest', + message: i18n(`${i18nKey}.enterDest`), + when: !options.dest && !skipDestPrompt, + validate: (input?: string) => { + if (!input) { + return i18n(`${i18nKey}.errors.destRequired`); + } + if (fs.existsSync(input)) { + return i18n(`${i18nKey}.errors.invalidDest`); + } + if (!isValidPath(input)) { + return i18n(`${i18nKey}.errors.invalidCharacters`); + } + return true; + }, + filter: (input: string) => { + return untildify(input); + }, + }, + ]); +} diff --git a/lib/prompts/selectPublicAppPrompt.ts b/lib/prompts/selectPublicAppPrompt.ts index 2f977ac12..eb1f62bba 100644 --- a/lib/prompts/selectPublicAppPrompt.ts +++ b/lib/prompts/selectPublicAppPrompt.ts @@ -1,25 +1,33 @@ -// @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 { + 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 ); @@ -47,18 +55,22 @@ const fetchPublicAppOptions = async ( } return filteredPublicApps; } catch (error) { - logError(error, { accountId }); + logError(error, accountId ? { accountId } : undefined); 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 +79,7 @@ const selectPublicAppPrompt = async ({ ? 'selectAppIdMigrate' : 'selectAppIdClone'; - return promptUser([ + return promptUser([ { name: 'appId', message: i18n(`${i18nKey}.${translationKey}`, { @@ -89,8 +101,4 @@ const selectPublicAppPrompt = async ({ }), }, ]); -}; - -module.exports = { - selectPublicAppPrompt, -}; +} diff --git a/lib/prompts/setAsDefaultAccountPrompt.ts b/lib/prompts/setAsDefaultAccountPrompt.ts index 870fb7b83..7f8ea61be 100644 --- a/lib/prompts/setAsDefaultAccountPrompt.ts +++ b/lib/prompts/setAsDefaultAccountPrompt.ts @@ -1,21 +1,23 @@ -// @ts-nocheck -const { - getConfig, +import { updateDefaultAccount, -} = require('@hubspot/local-dev-lib/config'); -const { promptUser } = require('./promptUtils'); -const { i18n } = require('../lang'); + getConfigDefaultAccount, +} from '@hubspot/local-dev-lib/config'; +import { promptUser } from './promptUtils'; +import { i18n } from '../lang'; const i18nKey = 'lib.prompts.setAsDefaultAccountPrompt'; -const setAsDefaultAccountPrompt = async accountName => { - const config = getConfig(); +export async function setAsDefaultAccountPrompt( + accountName: string +): Promise { + // Accounts for deprecated and new config + const defaultAccount = getConfigDefaultAccount(); const { setAsDefault } = await promptUser([ { name: 'setAsDefault', type: 'confirm', - when: config.defaultPortal !== accountName, + when: defaultAccount !== accountName, message: i18n(`${i18nKey}.setAsDefaultAccountMessage`), }, ]); @@ -24,8 +26,4 @@ const setAsDefaultAccountPrompt = async accountName => { updateDefaultAccount(accountName); } return setAsDefault; -}; - -module.exports = { - setAsDefaultAccountPrompt, -}; +} 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/lib/sandboxSync.ts b/lib/sandboxSync.ts index 2bbf16834..0b6587d3b 100644 --- a/lib/sandboxSync.ts +++ b/lib/sandboxSync.ts @@ -13,6 +13,9 @@ const { const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/index'); const { getSandboxTypeAsString } = require('./sandboxes'); const { getAccountId } = require('@hubspot/local-dev-lib/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); const { uiAccountDescription, uiLine, @@ -37,8 +40,10 @@ const syncSandbox = async ({ syncTasks, slimInfoMessage = false, }) => { - const accountId = getAccountId(accountConfig.portalId); - const parentAccountId = getAccountId(parentAccountConfig.portalId); + const id = getAccountIdentifier(accountConfig); + const accountId = getAccountId(id); + const parentId = getAccountIdentifier(parentAccountConfig); + const parentAccountId = getAccountId(parentId); const isDevSandbox = isDevelopmentSandbox(accountConfig); SpinniesManager.init({ succeedColor: 'white', diff --git a/lib/sandboxes.ts b/lib/sandboxes.ts index 456e8d0a9..2f0599870 100644 --- a/lib/sandboxes.ts +++ b/lib/sandboxes.ts @@ -6,9 +6,9 @@ const { } = require('@hubspot/local-dev-lib/api/sandboxHubs'); const { fetchTypes } = require('@hubspot/local-dev-lib/api/sandboxSync'); const { - getConfig, getAccountId, getEnv, + getConfigAccounts, } = require('@hubspot/local-dev-lib/config'); const { promptUser } = require('./prompts/promptUtils'); const { isDevelopmentSandbox } = require('./accountTypes'); @@ -16,6 +16,9 @@ const { getHubSpotWebsiteOrigin } = require('@hubspot/local-dev-lib/urls'); const { HUBSPOT_ACCOUNT_TYPES, } = require('@hubspot/local-dev-lib/constants/config'); +const { + getAccountIdentifier, +} = require('@hubspot/local-dev-lib/config/getAccountIdentifier'); const { uiAccountDescription } = require('./ui'); const { isMissingScopeError, @@ -48,9 +51,10 @@ const getSandboxTypeAsString = accountType => { }; function getHasSandboxesByType(parentAccountConfig, type) { - const config = getConfig(); - const parentPortalId = getAccountId(parentAccountConfig.portalId); - for (const portal of config.portals) { + const id = getAccountIdentifier(parentAccountConfig); + const parentPortalId = getAccountId(id); + const accountsList = getConfigAccounts(); + for (const portal of accountsList) { if ( (portal.parentAccountId !== null || portal.parentAccountId !== undefined) && @@ -72,8 +76,10 @@ function getSandboxLimit(error) { // Fetches available sync types for a given sandbox portal async function getAvailableSyncTypes(parentAccountConfig, config) { - const parentPortalId = getAccountId(parentAccountConfig.portalId); - const portalId = getAccountId(config.portalId); + const parentId = getAccountIdentifier(parentAccountConfig); + const parentPortalId = getAccountId(parentId); + const id = getAccountIdentifier(config); + const portalId = getAccountId(id); const { data: { results: syncTypes }, } = await fetchTypes(parentPortalId, portalId); @@ -128,7 +134,8 @@ const getSyncTypesWithContactRecordsPrompt = async ( * @returns {null} */ const validateSandboxUsageLimits = async (accountConfig, sandboxType, env) => { - const accountId = getAccountId(accountConfig.portalId); + const id = getAccountIdentifier(accountConfig); + const accountId = getAccountId(id); const { data: { usage }, } = await getSandboxUsageLimits(accountId); diff --git a/lib/ui/index.ts b/lib/ui/index.ts index 6799b9745..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/lib/usageTracking.ts b/lib/usageTracking.ts index 530dadc46..c1cbc735d 100644 --- a/lib/usageTracking.ts +++ b/lib/usageTracking.ts @@ -1,34 +1,28 @@ -// @ts-nocheck -const { trackUsage } = require('@hubspot/local-dev-lib/trackUsage'); -const { +import { trackUsage } from '@hubspot/local-dev-lib/trackUsage'; +import { isTrackingAllowed, getAccountConfig, -} = require('@hubspot/local-dev-lib/config'); -const { - API_KEY_AUTH_METHOD, -} = require('@hubspot/local-dev-lib/constants/auth'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { version } = require('../package.json'); -const { setLogLevel } = require('./commonOpts'); +} from '@hubspot/local-dev-lib/config'; +import { API_KEY_AUTH_METHOD } from '@hubspot/local-dev-lib/constants/auth'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { version } from '../package.json'; +import { debugError } from './errorHandlers'; -/* ** -Available tracking meta properties - - - action: "The specific action taken in the CLI" - - os: "The user's OS" - - nodeVersion: "The user's version of node.js" - - nodeMajorVersion: "The user's major version of node.js" - - version: "The user's version of the CLI" - - command: "The specific command that the user ran in this interaction" - - authType: "The configured auth type the user has for the CLI" - - step: "The specific step in the auth process" - - assetType: "The CMS asset type" - - mode: "The CLI mode (draft or publish" - - type: "The upload type (file or folder)" - - file: "Whether or not the 'file' flag was used" - - successful: "Whether or not the CLI interaction was successful" - -*/ +type Meta = { + action?: string; // "The specific action taken in the CLI" + os?: string; // "The user's OS" + nodeVersion?: string; // "The user's version of node.js" + nodeMajorVersion?: string; // "The user's major version of node.js" + version?: string; // "The user's version of the CLI" + command?: string; // "The specific command that the user ran in this interaction" + authType?: string; // "The configured auth type the user has for the CLI" + step?: string; // "The specific step in the process" + assetType?: string; // "The asset type" + mode?: string; // "The CMS publish mode (draft or publish)" + type?: string | number; // "The upload type" + file?: boolean; // "Whether or not the 'file' flag was used" + successful?: boolean; // "Whether or not the CLI interaction was successful" +}; const EventClass = { USAGE: 'USAGE', @@ -37,12 +31,17 @@ const EventClass = { ACTIVATION: 'ACTIVATION', }; -const getNodeVersionData = () => ({ - nodeVersion: process.version, - nodeMajorVersion: (process.version || '').split('.')[0], -}); +function getNodeVersionData(): { + nodeVersion: string; + nodeMajorVersion: string; +} { + return { + nodeVersion: process.version, + nodeMajorVersion: (process.version || '').split('.')[0], + }; +} -const getPlatform = () => { +function getPlatform(): string { switch (process.platform) { case 'darwin': return 'macos'; @@ -51,9 +50,13 @@ const getPlatform = () => { default: return process.platform; } -}; +} -export function trackCommandUsage(command, meta = {}, accountId) { +export async function trackCommandUsage( + command: string, + meta: Meta = {}, + accountId?: number +): Promise { if (!isTrackingAllowed()) { return; } @@ -85,12 +88,12 @@ export function trackCommandUsage(command, meta = {}, accountId) { ); logger.debug('Sent usage tracking command event: %o', usageTrackingEvent); } catch (e) { - logger.debug('Usage tracking failed: %s', e.message); + debugError(e); } }); } -async function trackHelpUsage(command) { +export async function trackHelpUsage(command: string): Promise { if (!isTrackingAllowed()) { return; } @@ -108,11 +111,11 @@ async function trackHelpUsage(command) { command, }); } catch (e) { - logger.debug('Usage tracking failed: %s', e.message); + debugError(e); } } -async function trackConvertFieldsUsage(command) { +export async function trackConvertFieldsUsage(command: string): Promise { if (!isTrackingAllowed()) { return; } @@ -126,18 +129,16 @@ async function trackConvertFieldsUsage(command) { command, }); } catch (e) { - logger.debug('Usage tracking failed: %s', e.message); + debugError(e); } } -const addHelpUsageTracking = (program, command) => { - program.on('--help', () => { - setLogLevel(program); - trackHelpUsage(command); - }); -}; - -const trackAuthAction = async (command, authType, step, accountId) => { +export async function trackAuthAction( + command: string, + authType: string, + step: string, + accountId: number +): Promise { if (!isTrackingAllowed()) { return; } @@ -160,11 +161,15 @@ const trackAuthAction = async (command, authType, step, accountId) => { logger.debug('Sent usage tracking command event: %o', usageTrackingEvent); } catch (e) { - logger.debug('Auth action tracking failed: %s', e.message); + debugError(e); } -}; +} -export function trackCommandMetadataUsage(command, meta = {}, accountId) { +export async function trackCommandMetadataUsage( + command: string, + meta: Meta = {}, + accountId?: number +): Promise { if (!isTrackingAllowed()) { return; } @@ -196,16 +201,7 @@ export function trackCommandMetadataUsage(command, meta = {}, accountId) { ); logger.debug('Sent usage tracking command event: %o', usageTrackingEvent); } catch (e) { - logger.debug('Metadata usage tracking failed: %s', e.message); + debugError(e); } }); } - -module.exports = { - trackCommandUsage, - trackHelpUsage, - addHelpUsageTracking, - trackConvertFieldsUsage, - trackAuthAction, - trackCommandMetadataUsage, -}; diff --git a/lib/validation.ts b/lib/validation.ts index 5da82310c..859ebde44 100644 --- a/lib/validation.ts +++ b/lib/validation.ts @@ -1,68 +1,35 @@ -// @ts-nocheck -const fs = require('fs'); -const path = require('path'); -const { logger } = require('@hubspot/local-dev-lib/logger'); -const { MODE } = require('@hubspot/local-dev-lib/constants/files'); -const { +import * as fs from 'fs'; +import * as path from 'path'; +import { Arguments } from 'yargs'; +import { logger } from '@hubspot/local-dev-lib/logger'; +import { CMS_PUBLISH_MODE } from '@hubspot/local-dev-lib/constants/files'; +import { CmsPublishMode } from '@hubspot/local-dev-lib/types/Files'; +import { API_KEY_AUTH_METHOD, OAUTH_AUTH_METHOD, PERSONAL_ACCESS_KEY_AUTH_METHOD, -} = require('@hubspot/local-dev-lib/constants/auth'); -const { commaSeparatedValues } = require('@hubspot/local-dev-lib/text'); -const { - loadConfig, +} from '@hubspot/local-dev-lib/constants/auth'; +import { commaSeparatedValues } from '@hubspot/local-dev-lib/text'; +import { getConfigPath, - validateConfig, getAccountConfig, loadConfigFromEnvironment, -} = require('@hubspot/local-dev-lib/config'); -const { getOauthManager } = require('@hubspot/local-dev-lib/oauth'); -const { - accessTokenForPersonalAccessKey, -} = require('@hubspot/local-dev-lib/personalAccessKey'); -const { +} from '@hubspot/local-dev-lib/config'; +import { getOauthManager } from '@hubspot/local-dev-lib/oauth'; +import { accessTokenForPersonalAccessKey } from '@hubspot/local-dev-lib/personalAccessKey'; +import { getAbsoluteFilePath, getCwd, getExt, -} = require('@hubspot/local-dev-lib/path'); -const { getAccountId, getMode, setLogLevel } = require('./commonOpts'); -const { EXIT_CODES } = require('./enums/exitCodes'); -const { checkAndWarnGitInclusion } = require('./ui/git'); -const { logError } = require('./errorHandlers/index'); - -async function loadAndValidateOptions(options, shouldValidateAccount = true) { - setLogLevel(options); - const { config: configPath } = options; - loadConfig(configPath, options); - checkAndWarnGitInclusion(getConfigPath()); - - let validAccount = true; - if (shouldValidateAccount) { - validAccount = await validateAccount(options); - } - - if (!(validateConfig() && validAccount)) { - process.exit(EXIT_CODES.ERROR); - } -} +} from '@hubspot/local-dev-lib/path'; +import { getAccountId, getCmsPublishMode } from './commonOpts'; +import { logError } from './errorHandlers/index'; -/** - * Validate that an account was passed to the command and that the account's configuration is valid - * - * - * @param {object} command options - * @returns {boolean} - */ -async function validateAccount(options) { +export async function validateAccount( + options: Arguments<{ account?: string; accountId?: string }> +): Promise { const accountId = getAccountId(options); - const { - portalId: portalIdOption, - portal: portalOption, - accountId: _accountIdOption, - account: _accountOption, - } = options; - const accountOption = portalOption || _accountOption; - const accountIdOption = portalIdOption || _accountIdOption; + const { accountId: accountIdOption, account: accountOption } = options; if (!accountId) { if (accountOption) { @@ -127,7 +94,11 @@ async function validateAccount(options) { const oauth = getOauthManager(accountId, accountConfig); try { - const accessToken = await oauth.accessToken(); + let accessToken: string | undefined; + + if (oauth) { + accessToken = await oauth.accessToken(); + } if (!accessToken) { logger.error( `The OAuth2 access token could not be found for accountId ${accountId}` @@ -135,7 +106,7 @@ async function validateAccount(options) { return false; } } catch (e) { - logger.error(e.message); + logError(e); return false; } } else if (authType === 'personalaccesskey') { @@ -168,28 +139,33 @@ async function validateAccount(options) { return true; } -/** - * @param {object} command options - * @returns {boolean} - */ -function validateMode(options) { - const mode = getMode(options); - if (MODE[mode]) { +export function validateCmsPublishMode( + options: Arguments<{ cmsPublishMode?: CmsPublishMode }> +): boolean { + const cmsPublishMode = getCmsPublishMode(options); + if (CMS_PUBLISH_MODE[cmsPublishMode]) { return true; } - const modesMessage = `Available modes are: ${Object.values(MODE).join( - ', ' - )}.`; - if (mode != null) { - logger.error([`The mode "${mode}" is invalid.`, modesMessage].join(' ')); + const modesMessage = `Available CMS publish modes are: ${Object.values( + CMS_PUBLISH_MODE + ).join(', ')}.`; + if (cmsPublishMode != null) { + logger.error( + [ + `The CMS publish mode "${cmsPublishMode}" is invalid.`, + modesMessage, + ].join(' ') + ); } else { - logger.error(['The mode option is missing.', modesMessage].join(' ')); + logger.error( + ['The CMS publish mode option is missing.', modesMessage].join(' ') + ); } return false; } -const fileExists = _path => { - let isFile; +export function fileExists(_path: string): boolean { + let isFile: boolean; try { const absoluteSrcPath = path.resolve(getCwd(), _path); if (!absoluteSrcPath) return false; @@ -207,11 +183,11 @@ const fileExists = _path => { } return true; -}; +} -const checkAndConvertToJson = _path => { +export function checkAndConvertToJson(_path: string): object | null { const filePath = getAbsoluteFilePath(_path); - if (!fileExists(filePath)) return false; + if (!fileExists(filePath)) return null; if (getExt(_path) !== 'json') { logger.error(`The file "${_path}" must be a valid JSON file`); @@ -228,12 +204,4 @@ const checkAndConvertToJson = _path => { } return result; -}; - -module.exports = { - validateMode, - validateAccount, - checkAndConvertToJson, - fileExists, - loadAndValidateOptions, -}; +} diff --git a/package.json b/package.json index 0a13fe549..4c08c7090 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "@hubspot/cli", - "version": "6.4.1-beta.0", + "version": "7.0.0-experimental.2", "description": "The official CLI for developing on HubSpot", "license": "Apache-2.0", "repository": "https://github.com/HubSpot/hubspot-cli", "dependencies": { - "@hubspot/local-dev-lib": "2.3.0", + "@hubspot/local-dev-lib": "3.1.0", "@hubspot/serverless-dev-runtime": "7.0.0", - "@hubspot/theme-preview-dev-server": "0.0.9", + "@hubspot/theme-preview-dev-server": "0.0.10", "@hubspot/ui-extensions-dev-server": "0.8.33", "archiver": "^7.0.1", "chalk": "^4.1.2", @@ -26,13 +26,18 @@ "table": "^6.6.0", "tmp": "^0.2.1", "update-notifier": "^5.1.0", - "yargs": "17.7.2" + "yargs": "17.7.2", + "yargs-parser": "^21.1.1" }, "devDependencies": { + "@types/archiver": "^6.0.3", + "@types/express": "^5.0.0", + "@types/findup-sync": "^4.0.5", "@types/fs-extra": "^11.0.4", "@types/jest": "^29.5.14", "@types/js-yaml": "^4.0.9", "@types/semver": "^7.5.8", + "@types/tmp": "^0.2.6", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.11.0", "@typescript-eslint/parser": "^8.11.0", @@ -44,7 +49,7 @@ "lint-staged": "^10.5.4", "madge": "^8.0.0", "mock-stdin": "^1.0.0", - "prettier": "^1.19.1", + "prettier": "^3.4.2", "semver": "^7.6.3", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", @@ -56,7 +61,8 @@ "scripts": { "build": "ts-node ./scripts/build.ts", "lint": "eslint . && prettier --list-different ./**/*.{js,json}", - "prettier:write": "prettier --write ./**/*.{js,json}", + "list-all-commands": "yarn ts-node ./scripts/get-all-commands.ts", + "prettier:write": "prettier --write ./**/*.{ts,js,json}", "test": "jest", "test-cli": "yarn --cwd 'acceptance-tests' test-ci", "test-cli-debug": "yarn --cwd 'acceptance-tests' test-debug", diff --git a/scripts/get-all-commands.ts b/scripts/get-all-commands.ts new file mode 100644 index 000000000..967231fb3 --- /dev/null +++ b/scripts/get-all-commands.ts @@ -0,0 +1,58 @@ +import { exec } from 'child_process'; +import SpinniesManager from '../lib/ui/SpinniesManager'; +import util from 'util'; + +const output = {}; + +const execAsync = util.promisify(exec); + +function generateCommand(parent: string, command: string) { + const parentCommand = parent !== '' ? `${parent} ` : ''; + return `hs ${parentCommand}${command} --get-yargs-completions`; +} + +async function extractCommands( + toParse: string, + parent: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + commands: any +) { + for (const line of toParse.split('\n')) { + const [arg] = line.split(':'); + + if ( + !commands[arg] && + // filter out flags, empty strings from splitting, and the completion command + // because they lead to infinite loops because they display the root level commands + // all over again + !arg.startsWith('-') && + arg !== '' && + arg !== 'completion' + ) { + commands[arg] = {}; + + const { stdout } = await execAsync(generateCommand(parent, arg)); + await extractCommands( + stdout, + // Concatenate the parent command with the current command to recurse the subcommands + `${parent !== '' ? `${parent} ` : ''}${arg}`, + commands[arg] + ); + } + } + return commands; +} + +(async function() { + SpinniesManager.init(); + SpinniesManager.add('extractingCommands', { text: 'Extracting commands' }); + + const { stdout } = await execAsync(generateCommand('', '')); + const result = await extractCommands(stdout, '', output); + + SpinniesManager.succeed('extractingCommands', { + text: 'Done extracting commands', + }); + + console.log(result); +})(); diff --git a/scripts/release.ts b/scripts/release.ts index 3e3952f2c..680d42d4b 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -51,8 +51,8 @@ const PRERELEASE_IDENTIFIER = { const REGISTRY = publishConfig.registry; type ReleaseArguments = { - versionIncrement: typeof VERSION_INCREMENT_OPTIONS[number]; - tag: typeof TAG_OPTIONS[number]; + versionIncrement: (typeof VERSION_INCREMENT_OPTIONS)[number]; + tag: (typeof TAG_OPTIONS)[number]; dryRun?: boolean; }; @@ -62,7 +62,7 @@ type DistTags = { [TAG.EXPERIMENTAL]: string; }; -type Tag = typeof TAG_OPTIONS[number]; +type Tag = (typeof TAG_OPTIONS)[number]; async function getGitBranch(): Promise { const { stdout } = await exec('git rev-parse --abbrev-ref HEAD'); @@ -191,10 +191,8 @@ async function handler({ process.exit(EXIT_CODES.ERROR); } - const { - next: currentNextTag, - experimental: currentExperimentalTag, - } = await getDistTags(); + const { next: currentNextTag, experimental: currentExperimentalTag } = + await getDistTags(); if (!isExperimental && currentNextTag !== localVersion) { logger.error( diff --git a/types/Projects.ts b/types/Projects.ts new file mode 100644 index 000000000..e6010faeb --- /dev/null +++ b/types/Projects.ts @@ -0,0 +1,51 @@ +import { Build, SubbuildStatus } from '@hubspot/local-dev-lib/types/Build'; +import { Deploy, SubdeployStatus } from '@hubspot/local-dev-lib/types/Deploy'; + +export type ProjectTemplate = { + name: string; + label: string; + path: string; + insertPath: string; +}; + +export type ComponentTemplate = { + label: string; + path: string; + insertPath: string; +}; + +export type ProjectConfig = { + name: string; + srcDir: string; + platformVersion: string; +}; + +export type ProjectTaskStates = { + BUILDING?: string; + ENQUEUED?: string; + DEPLOYING?: string; + FAILURE: string; + PENDING: string; + SUCCESS: string; +}; + +export type ProjectTask = Build | Deploy; +export type ProjectSubtask = SubbuildStatus | SubdeployStatus; + +export type ProjectPollStatusFunctionText = { + STATES: ProjectTaskStates; + STATUS_TEXT: string; + TYPE_KEY: string; + SUBTASK_NAME_KEY: string; +}; + +export type ProjectAddComponentData = { + path: string; + label: string; + insertPath: string; +}; + +export type ProjectTemplateRepoConfig = { + projects?: ProjectTemplate[]; + components?: ComponentTemplate[]; +}; diff --git a/types/Prompts.ts b/types/Prompts.ts new file mode 100644 index 000000000..db703d253 --- /dev/null +++ b/types/Prompts.ts @@ -0,0 +1,42 @@ +export type GenericPromptResponse = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +}; + +type PromptType = + | 'confirm' + | 'list' + | 'checkbox' + | 'input' + | 'password' + | 'number' + | 'rawlist'; + +export type PromptChoices = Array< + | string + | { + name: string; + // 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[] | null; + +export type PromptConfig = { + name: keyof T; + type?: PromptType; + message?: string | ((answers: T) => string); + choices?: PromptChoices; + when?: PromptWhen; + pageSize?: number; + default?: PromptOperand | ((answers: T) => PromptOperand); + transformer?: (input: string) => string | undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + validate?: (answer?: any) => PromptOperand | Promise; + mask?: string; + filter?: (input: string) => string; +}; diff --git a/yarn.lock b/yarn.lock index c06589fb9..1a8ff8a26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,7 +17,7 @@ dependencies: default-browser-id "3.0.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -27,11 +27,11 @@ picocolors "^1.0.0" "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" - integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" + integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.20.12", "@babel/core@^7.22.9", "@babel/core@^7.23.9", "@babel/core@^7.25.2", "@babel/core@^7.7.5": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.20.12", "@babel/core@^7.22.9", "@babel/core@^7.23.9", "@babel/core@^7.26.0", "@babel/core@^7.7.5": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== @@ -52,13 +52,13 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.12.11", "@babel/generator@^7.22.9", "@babel/generator@^7.25.9", "@babel/generator@^7.26.0", "@babel/generator@^7.7.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" - integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== +"@babel/generator@^7.12.11", "@babel/generator@^7.22.9", "@babel/generator@^7.26.0", "@babel/generator@^7.26.3", "@babel/generator@^7.7.2": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.3.tgz#ab8d4360544a425c90c248df7059881f4b2ce019" + integrity sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ== dependencies: - "@babel/parser" "^7.26.2" - "@babel/types" "^7.26.0" + "@babel/parser" "^7.26.3" + "@babel/types" "^7.26.3" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" @@ -70,14 +70,6 @@ dependencies: "@babel/types" "^7.25.9" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz#f41752fe772a578e67286e6779a68a5a92de1ee9" - integrity sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" @@ -103,12 +95,12 @@ semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz#3e8999db94728ad2b2458d7a470e7770b7764e26" - integrity sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" + integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== dependencies: "@babel/helper-annotate-as-pure" "^7.25.9" - regexpu-core "^6.1.1" + regexpu-core "^6.2.0" semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3": @@ -177,14 +169,6 @@ "@babel/helper-optimise-call-expression" "^7.25.9" "@babel/traverse" "^7.25.9" -"@babel/helper-simple-access@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz#6d51783299884a2c74618d6ef0f86820ec2e7739" - integrity sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" @@ -225,12 +209,12 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.25.3", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" - integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.4", "@babel/parser@^7.25.3", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.3.tgz#8c51c5db6ddf08134af1ddbacf16aaab48bac234" + integrity sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA== dependencies: - "@babel/types" "^7.26.0" + "@babel/types" "^7.26.3" "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": version "7.25.9" @@ -555,11 +539,10 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-exponentiation-operator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz#ece47b70d236c1d99c263a1e22b62dc20a4c8b0f" - integrity sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" + integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.25.9" "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-export-namespace-from@^7.25.9": @@ -631,13 +614,12 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz#d165c8c569a080baf5467bda88df6425fc060686" - integrity sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" + integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== dependencies: - "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-simple-access" "^7.25.9" "@babel/plugin-transform-modules-systemjs@^7.25.9": version "7.25.9" @@ -749,14 +731,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-transform-react-jsx-self@^7.18.6", "@babel/plugin-transform-react-jsx-self@^7.24.7": +"@babel/plugin-transform-react-jsx-self@^7.18.6", "@babel/plugin-transform-react-jsx-self@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858" integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg== dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-transform-react-jsx-source@^7.19.6", "@babel/plugin-transform-react-jsx-source@^7.24.7": +"@babel/plugin-transform-react-jsx-source@^7.19.6", "@babel/plugin-transform-react-jsx-source@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz#4c6b8daa520b5f155b5fb55547d7c9fa91417503" integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg== @@ -823,9 +805,9 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-typescript@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz#69267905c2b33c2ac6d8fe765e9dc2ddc9df3849" - integrity sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ== + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz#3d6add9c78735623317387ee26d5ada540eee3fd" + integrity sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA== dependencies: "@babel/helper-annotate-as-pure" "^7.25.9" "@babel/helper-create-class-features-plugin" "^7.25.9" @@ -996,22 +978,22 @@ "@babel/types" "^7.25.9" "@babel/traverse@^7.1.6", "@babel/traverse@^7.22.8", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" - integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + version "7.26.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.4.tgz#ac3a2a84b908dde6d463c3bfa2c5fdc1653574bd" + integrity sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w== dependencies: - "@babel/code-frame" "^7.25.9" - "@babel/generator" "^7.25.9" - "@babel/parser" "^7.25.9" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.3" + "@babel/parser" "^7.26.3" "@babel/template" "^7.25.9" - "@babel/types" "^7.25.9" + "@babel/types" "^7.26.3" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.25.2", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" - integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.25.2", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.3.tgz#37e79830f04c2b5687acc77db97fbc75fb81f3c0" + integrity sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA== dependencies: "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" @@ -1052,9 +1034,9 @@ integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== "@emotion/use-insertion-effect-with-fallbacks@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" - integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== "@esbuild/aix-ppc64@0.21.5": version "0.21.5" @@ -1377,28 +1359,29 @@ express "^4.18.2" uuid "^9.0.1" -"@hubspot/cms-components@^0.18.10": - version "0.18.10" - resolved "https://registry.yarnpkg.com/@hubspot/cms-components/-/cms-components-0.18.10.tgz#3d74218e35e4887d05bb79063f6fbf7b1118a553" - integrity sha512-Qm8ypryApK7raqjPiq9305XQGcz7JhxYe6GTlYtgZ5JgXWK8rTs9lupYQ4d0M55+H1puSX81cPQ6XwZyi2ADNQ== +"@hubspot/cms-components@^0.18.11": + version "0.18.11" + resolved "https://registry.yarnpkg.com/@hubspot/cms-components/-/cms-components-0.18.11.tgz#5ad3992c228edcbdae56ecfd4850a73e2833e0b1" + integrity sha512-AeRBuFwFhyiz3TVoPxEOsZliohG6JwJa2tW7UInNxGvzQXwejiCYoyS1iq5+gaHUyMZcZRIxzRHrYTf5NY5QsA== dependencies: dot-prop "^8.0.2" react "18.3.1" react-dom "18.3.1" "@hubspot/cms-dev-server@^0.18.10": - version "0.18.10" - resolved "https://registry.yarnpkg.com/@hubspot/cms-dev-server/-/cms-dev-server-0.18.10.tgz#db7737ea03aea035e6b3f4c12e061da33d850475" - integrity sha512-khuA4tocob2P77zW/AZ9k2RxFdfj0Oju7HtbGM7TGdw4o0gddF25bXHYD+IG+p0CgZdr7Vv0KvouVNrkPwyM1g== + version "0.18.11" + resolved "https://registry.yarnpkg.com/@hubspot/cms-dev-server/-/cms-dev-server-0.18.11.tgz#d58c077286338b8a47ee2ff573bc51e54b1ac2e3" + integrity sha512-8VA8Z4g/Idj7fzFIEEe9BaVViyHjnw+Hw6BFDfArs1oh0VVnYuCL9Cgwa+zgSZ4qbMdCtwxlG7I1Gx0wumlTsQ== dependencies: "@babel/code-frame" "^7.24.7" "@babel/parser" "^7.25.3" "@babel/traverse" "^7.25.3" "@babel/types" "^7.25.2" - "@hubspot/cms-components" "^0.18.10" + "@hubspot/cms-components" "^0.18.11" "@hubspot/local-dev-lib" "2.0.1" "@inquirer/select" "^2.0.0" "@originjs/vite-plugin-commonjs" "^1.0.3" + "@radix-ui/react-dialog" "^1.1.2" "@radix-ui/react-separator" "^1.1.0" "@radix-ui/react-slot" "^1.1.0" "@radix-ui/react-tooltip" "^1.1.4" @@ -1463,10 +1446,10 @@ semver "^6.3.0" unixify "^1.0.0" -"@hubspot/local-dev-lib@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-2.3.0.tgz#3350b9902adb00b1b927d049197e7333501d90fe" - integrity sha512-ovWE0XjuzzdyJoQ9rFXIYLotNCfowfMbN+8eIlBUu89uzBcWUZj+VQudU6GN15RlPS8ZTSSvVFnCcMxNJv1IKw== +"@hubspot/local-dev-lib@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@hubspot/local-dev-lib/-/local-dev-lib-3.1.0.tgz#76b5d524aa694aad2bfc6e199ee1dac314f15774" + integrity sha512-iop3PgZ0ZWejCH6PmSeYnnGwV8vGIuA4F+w7SxukdX3QfhivlczClATWPaZanv1CYJHdflqoItq1kdF8ASJJmA== dependencies: address "^2.0.1" axios "^1.3.5" @@ -1503,10 +1486,10 @@ table "^6.8.1" tmp "^0.2.1" -"@hubspot/theme-preview-dev-server@0.0.9": - version "0.0.9" - resolved "https://registry.yarnpkg.com/@hubspot/theme-preview-dev-server/-/theme-preview-dev-server-0.0.9.tgz#51ce32156365527dfd2d7044f5a87f90d6434283" - integrity sha512-w6a6E9f6DVbUcVGStEFakUL9voUMfyuYZRVsoIyaWR5fYlejmn1MJnpMlkU25W2nRWDwkr8RgdYMvZ/DstJUzw== +"@hubspot/theme-preview-dev-server@0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@hubspot/theme-preview-dev-server/-/theme-preview-dev-server-0.0.10.tgz#c349d1c1d28d8349e09b0828ef49911d651d78cf" + integrity sha512-ZhKlFS+Z9ZZXrKyFGVq1oWRF+StMY/wKVlQ5atLpdfPhzvue40ZwEQLsM5NT0jYnlyHXi9ojCz645wUVxNq78w== dependencies: "@types/node-fetch" "^2.6.11" chokidar "^3.6.0" @@ -1825,9 +1808,9 @@ react-docgen-typescript "^2.2.2" "@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== dependencies: "@jridgewell/set-array" "^1.2.1" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -2035,44 +2018,73 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@radix-ui/primitive@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2" - integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA== +"@radix-ui/primitive@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.1.tgz#fc169732d755c7fbad33ba8d0cd7fd10c90dc8e3" + integrity sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA== -"@radix-ui/react-arrow@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a" - integrity sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw== +"@radix-ui/react-arrow@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz#2103721933a8bfc6e53bbfbdc1aaad5fc8ba0dd7" + integrity sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w== dependencies: - "@radix-ui/react-primitive" "2.0.0" - -"@radix-ui/react-compose-refs@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74" - integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw== + "@radix-ui/react-primitive" "2.0.1" -"@radix-ui/react-context@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8" - integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A== +"@radix-ui/react-compose-refs@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz#6f766faa975f8738269ebb8a23bad4f5a8d2faec" + integrity sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw== "@radix-ui/react-context@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a" integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q== -"@radix-ui/react-dismissable-layer@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396" - integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ== +"@radix-ui/react-dialog@^1.1.2": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz#d68e977acfcc0d044b9dab47b6dd2c179d2b3191" + integrity sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA== + dependencies: + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.3" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.1" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "^2.6.1" + +"@radix-ui/react-dismissable-layer@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz#4ee0f0f82d53bf5bd9db21665799bb0d1bad5ed8" + integrity sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-escape-keydown" "1.1.0" +"@radix-ui/react-focus-guards@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe" + integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg== + +"@radix-ui/react-focus-scope@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz#5c602115d1db1c4fcfa0fae4c3b09bb8919853cb" + integrity sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA== + dependencies: + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-id@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed" @@ -2080,76 +2092,76 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-popper@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a" - integrity sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg== +"@radix-ui/react-popper@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.1.tgz#2fc66cfc34f95f00d858924e3bee54beae2dff0a" + integrity sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw== dependencies: "@floating-ui/react-dom" "^2.0.0" - "@radix-ui/react-arrow" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" - "@radix-ui/react-context" "1.1.0" - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-arrow" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" "@radix-ui/react-use-rect" "1.1.0" "@radix-ui/react-use-size" "1.1.0" "@radix-ui/rect" "1.1.0" -"@radix-ui/react-portal@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e" - integrity sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg== +"@radix-ui/react-portal@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.3.tgz#b0ea5141103a1671b715481b13440763d2ac4440" + integrity sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-presence@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1" - integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A== +"@radix-ui/react-presence@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.2.tgz#bb764ed8a9118b7ec4512da5ece306ded8703cdc" + integrity sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg== dependencies: - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-primitive@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884" - integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw== +"@radix-ui/react-primitive@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz#6d9efc550f7520135366f333d1e820cf225fad9e" + integrity sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg== dependencies: - "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-separator@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e" - integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.1.tgz#dd60621553c858238d876be9b0702287424866d2" + integrity sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" -"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84" - integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw== +"@radix-ui/react-slot@1.1.1", "@radix-ui/react-slot@^1.1.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.1.tgz#ab9a0ffae4027db7dc2af503c223c978706affc3" + integrity sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g== dependencies: - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-tooltip@^1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz#152d8485859b80d395d6b3229f676fef3cec56b3" - integrity sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw== + version "1.1.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz#eab98e9a5c876ef0abfae3cfeee229870528ed06" + integrity sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA== dependencies: - "@radix-ui/primitive" "1.1.0" - "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/primitive" "1.1.1" + "@radix-ui/react-compose-refs" "1.1.1" "@radix-ui/react-context" "1.1.1" - "@radix-ui/react-dismissable-layer" "1.1.1" + "@radix-ui/react-dismissable-layer" "1.1.3" "@radix-ui/react-id" "1.1.0" - "@radix-ui/react-popper" "1.2.0" - "@radix-ui/react-portal" "1.1.2" - "@radix-ui/react-presence" "1.1.1" - "@radix-ui/react-primitive" "2.0.0" - "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-popper" "1.2.1" + "@radix-ui/react-portal" "1.1.3" + "@radix-ui/react-presence" "1.1.2" + "@radix-ui/react-primitive" "2.0.1" + "@radix-ui/react-slot" "1.1.1" "@radix-ui/react-use-controllable-state" "1.1.0" - "@radix-ui/react-visually-hidden" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.1" "@radix-ui/react-use-callback-ref@1.1.0": version "1.1.0" @@ -2189,12 +2201,12 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" -"@radix-ui/react-visually-hidden@1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" - integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== +"@radix-ui/react-visually-hidden@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz#f7b48c1af50dfdc366e92726aee6d591996c5752" + integrity sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg== dependencies: - "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-primitive" "2.0.1" "@radix-ui/rect@1.1.0": version "1.1.0" @@ -2210,103 +2222,108 @@ picomatch "^2.2.2" "@rollup/pluginutils@^5.0.2": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.3.tgz#3001bf1a03f3ad24457591f2c259c8e514e0dbdf" - integrity sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A== + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== dependencies: "@types/estree" "^1.0.0" estree-walker "^2.0.2" picomatch "^4.0.2" -"@rollup/rollup-android-arm-eabi@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz#ab2c78c43e4397fba9a80ea93907de7a144f3149" - integrity sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ== - -"@rollup/rollup-android-arm64@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz#de840660ab65cf73bd6d4bc62d38acd9fc94cd6c" - integrity sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw== - -"@rollup/rollup-darwin-arm64@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz#8c786e388f7eff0d830151a9d8fbf04c031bb07f" - integrity sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA== - -"@rollup/rollup-darwin-x64@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz#56dab9e4cac0ad97741740ea1ac7b6a576e20e59" - integrity sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg== - -"@rollup/rollup-freebsd-arm64@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz#bcb4112cb7e68a12d148b03cbc21dde43772f4bc" - integrity sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw== - -"@rollup/rollup-freebsd-x64@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz#c7cd9f69aa43847b37d819f12c2ad6337ec245fa" - integrity sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA== - -"@rollup/rollup-linux-arm-gnueabihf@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz#3692b22987a6195c8490bbf6357800e0c183ee38" - integrity sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q== - -"@rollup/rollup-linux-arm-musleabihf@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz#f920f24e571f26bbcdb882267086942fdb2474bf" - integrity sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg== - -"@rollup/rollup-linux-arm64-gnu@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz#2046553e91d8ca73359a2a3bb471826fbbdcc9a3" - integrity sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ== - -"@rollup/rollup-linux-arm64-musl@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz#8a3f05dbae753102ae10a9bc2168c7b6bbeea5da" - integrity sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g== - -"@rollup/rollup-linux-powerpc64le-gnu@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz#d281d9c762f9e4f1aa7909a313f7acbe78aced32" - integrity sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw== - -"@rollup/rollup-linux-riscv64-gnu@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz#fa84b3f81826cee0de9e90f9954f3e55c3cc6c97" - integrity sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A== - -"@rollup/rollup-linux-s390x-gnu@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz#6b9c04d84593836f942ceb4dd90644633d5fe871" - integrity sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA== - -"@rollup/rollup-linux-x64-gnu@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz#f13effcdcd1cc14b26427e6bec8c6c9e4de3773e" - integrity sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA== - -"@rollup/rollup-linux-x64-musl@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz#6547bc0069f2d788e6cf0f33363b951181f4cca5" - integrity sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ== - -"@rollup/rollup-win32-arm64-msvc@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz#3f2db9347c5df5e6627a7e12d937cea527d63526" - integrity sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw== - -"@rollup/rollup-win32-ia32-msvc@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz#54fcf9a13a98d3f0e4be6a4b6e28b9dca676502f" - integrity sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w== - -"@rollup/rollup-win32-x64-msvc@4.27.3": - version "4.27.3" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz#3721f601f973059bfeeb572992cf0dfc94ab2970" - integrity sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg== +"@rollup/rollup-android-arm-eabi@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz#7f4c4d8cd5ccab6e95d6750dbe00321c1f30791e" + integrity sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ== + +"@rollup/rollup-android-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz#17ea71695fb1518c2c324badbe431a0bd1879f2d" + integrity sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA== + +"@rollup/rollup-darwin-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz#dac0f0d0cfa73e7d5225ae6d303c13c8979e7999" + integrity sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ== + +"@rollup/rollup-darwin-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz#8f63baa1d31784904a380d2e293fa1ddf53dd4a2" + integrity sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ== + +"@rollup/rollup-freebsd-arm64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz#30ed247e0df6e8858cdc6ae4090e12dbeb8ce946" + integrity sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA== + +"@rollup/rollup-freebsd-x64@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz#57846f382fddbb508412ae07855b8a04c8f56282" + integrity sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz#378ca666c9dae5e6f94d1d351e7497c176e9b6df" + integrity sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA== + +"@rollup/rollup-linux-arm-musleabihf@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz#a692eff3bab330d5c33a5d5813a090c15374cddb" + integrity sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg== + +"@rollup/rollup-linux-arm64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz#6b1719b76088da5ac1ae1feccf48c5926b9e3db9" + integrity sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA== + +"@rollup/rollup-linux-arm64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz#865baf5b6f5ff67acb32e5a359508828e8dc5788" + integrity sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A== + +"@rollup/rollup-linux-loongarch64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz#23c6609ba0f7fa7a7f2038b6b6a08555a5055a87" + integrity sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz#652ef0d9334a9f25b9daf85731242801cb0fc41c" + integrity sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A== + +"@rollup/rollup-linux-riscv64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz#1eb6651839ee6ebca64d6cc64febbd299e95e6bd" + integrity sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA== + +"@rollup/rollup-linux-s390x-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz#015c52293afb3ff2a293cf0936b1d43975c1e9cd" + integrity sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg== + +"@rollup/rollup-linux-x64-gnu@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz#b83001b5abed2bcb5e2dbeec6a7e69b194235c1e" + integrity sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw== + +"@rollup/rollup-linux-x64-musl@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz#6cc7c84cd4563737f8593e66f33b57d8e228805b" + integrity sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g== + +"@rollup/rollup-win32-arm64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz#631ffeee094d71279fcd1fe8072bdcf25311bc11" + integrity sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A== + +"@rollup/rollup-win32-ia32-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz#06d1d60d5b9f718e8a6c4a43f82e3f9e3254587f" + integrity sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA== + +"@rollup/rollup-win32-x64-msvc@4.28.1": + version "4.28.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz#4dff5c4259ebe6c5b4a8f2c5bc3829b7a8447ff0" + integrity sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA== "@rtsao/scc@^1.1.0": version "1.1.0" @@ -2903,9 +2920,9 @@ lodash "^4.17.15" "@storybook/csf@^0.1.0": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.11.tgz#ad685a4fe564a47a6b73571c2e7c07b526f4f71b" - integrity sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg== + version "0.1.12" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.1.12.tgz#1dcfa0f398a69b834c563884b5f747db3d5a81df" + integrity sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw== dependencies: type-fest "^2.19.0" @@ -3177,32 +3194,32 @@ dependencies: "@babel/runtime" "^7.12.5" -"@ts-graphviz/adapter@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@ts-graphviz/adapter/-/adapter-2.0.5.tgz#81dad5e97a79ffaff485bd4fb0dcd387dd6c8a3b" - integrity sha512-K/xd2SJskbSLcUz9uYW9IDy26I3Oyutj/LREjJgcuLMxT3um4sZfy9LiUhGErHjxLRaNcaDVGSsmWeiNuhidXg== +"@ts-graphviz/adapter@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@ts-graphviz/adapter/-/adapter-2.0.6.tgz#18d5a42304dca7ffff760fcaf311a3148ef4a3bd" + integrity sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q== dependencies: - "@ts-graphviz/common" "^2.1.4" + "@ts-graphviz/common" "^2.1.5" -"@ts-graphviz/ast@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@ts-graphviz/ast/-/ast-2.0.5.tgz#224825cf153abcaf255d9dc9c07e167bf15fc546" - integrity sha512-HVT+Bn/smDzmKNJFccwgrpJaEUMPzXQ8d84JcNugzTHNUVgxAIe2Vbf4ug351YJpowivQp6/N7XCluQMjtgi5w== +"@ts-graphviz/ast@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@ts-graphviz/ast/-/ast-2.0.6.tgz#d027b011934f9b933f7f9ba4f3ec712447cc14c2" + integrity sha512-JbOnw6+Pm+C9jRQlNV+qJG0/VTan4oCeZ0sClm++SjaaMBJ0q86O13i6wbcWKY2x8kKt9GP2hVCgM/p/BXtXWQ== dependencies: - "@ts-graphviz/common" "^2.1.4" + "@ts-graphviz/common" "^2.1.5" -"@ts-graphviz/common@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@ts-graphviz/common/-/common-2.1.4.tgz#9523a0f56e1a13467df070488de16cebf196c0e4" - integrity sha512-PNEzOgE4vgvorp/a4Ev26jVNtiX200yODoyPa8r6GfpPZbxWKW6bdXF6xWqzMkQoO1CnJOYJx2VANDbGqCqCCw== +"@ts-graphviz/common@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@ts-graphviz/common/-/common-2.1.5.tgz#a256dfaea009a5b147d8f73f25e57fb44f6462a2" + integrity sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg== -"@ts-graphviz/core@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@ts-graphviz/core/-/core-2.0.5.tgz#89c084d936f1bdfc01b0f16d8c3670c0eab5aa5a" - integrity sha512-YwaCGAG3Hs0nhxl+2lVuwuTTAK3GO2XHqOGvGIwXQB16nV858rrR5w2YmWCw9nhd11uLTStxLsCAhI9koWBqDA== +"@ts-graphviz/core@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@ts-graphviz/core/-/core-2.0.6.tgz#a9728dacd2e78c873079956bf46a23e2edcde50c" + integrity sha512-0hvrluFirC0ph3Dn2o1B0O1fI2n7Hre1HlScfmRcO6DDDq/05Vizg5UMI0LfvkJulLuz80RPjUHluh+QfBUBKw== dependencies: - "@ts-graphviz/ast" "^2.0.5" - "@ts-graphviz/common" "^2.1.4" + "@ts-graphviz/ast" "^2.0.6" + "@ts-graphviz/common" "^2.1.5" "@tsconfig/node10@^1.0.7": version "1.0.11" @@ -3224,6 +3241,13 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/archiver@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/archiver/-/archiver-6.0.3.tgz#074eb6f4febc0128c25a205a8263da3d4688df53" + integrity sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ== + dependencies: + "@types/readdir-glob" "*" + "@types/aria-query@^5.0.1": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" @@ -3270,6 +3294,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/braces@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.4.tgz#403488dc1c8d0db288270d3bbf0ce5f9c45678b4" + integrity sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA== + "@types/connect@*": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -3329,6 +3358,16 @@ "@types/range-parser" "*" "@types/send" "*" +"@types/express-serve-static-core@^5.0.0": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz#812d2871e5eea17fb0bd5214dda7a7b748c0e12a" + integrity sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + "@types/express@^4.7.0": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" @@ -3339,11 +3378,28 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/express@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/find-cache-dir@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.1.tgz#7b959a4b9643a1e6a1a5fe49032693cc36773501" integrity sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw== +"@types/findup-sync@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/findup-sync/-/findup-sync-4.0.5.tgz#df25b72fb256a65b346bc66e2dcd7de04393ec84" + integrity sha512-Y4NCs+7uDZ3SFF0GWowN2IANqOJr+Cdvp9hfMSGzQYqJkyvAEJIq/s0/8AP88fq+yswykWPWZRBrqWPEhCLsgg== + dependencies: + "@types/micromatch" "^4.0.0" + "@types/fs-extra@^11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" @@ -3438,6 +3494,13 @@ resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== +"@types/micromatch@^4.0.0": + version "4.0.9" + resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.9.tgz#8e5763a8c1fc7fbf26144d9215a01ab0ff702dbb" + integrity sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg== + dependencies: + "@types/braces" "*" + "@types/mime-types@^2.1.0": version "2.1.4" resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.4.tgz#93a1933e24fed4fb9e4adc5963a63efcbb3317a2" @@ -3469,16 +3532,16 @@ form-data "^4.0.0" "@types/node@*", "@types/node@^22.5.5": - version "22.9.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.9.1.tgz#bdf91c36e0e7ecfb7257b2d75bf1b206b308ca71" - integrity sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg== + version "22.10.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" + integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== dependencies: - undici-types "~6.19.8" + undici-types "~6.20.0" "@types/node@^16.0.0": - version "16.18.119" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.119.tgz#88443bb82119b7c0920e86949673876cbe1c3492" - integrity sha512-ia7V9a2FnhUFfetng4/sRPBMTwHZUkPFY736rb1cg9AgG7MZdR97q7/nLR9om+sq5f1la9C857E0l/nrI0RiFQ== + version "16.18.122" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.122.tgz#54948ddbe2ddef8144ee16b37f160e3f99c32397" + integrity sha512-rF6rUBS80n4oK16EW8nE75U+9fw0SSUgoPtWSvHhPXdT7itbvmS7UjB/jyM8i3AkvI6yeSM5qCwo+xN0npGDHg== "@types/normalize-package-data@^2.4.0": version "2.4.4" @@ -3495,11 +3558,6 @@ resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#ee1bd8c9f7a01b3445786aad0ef23aba5f511a44" integrity sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA== -"@types/prop-types@*": - version "15.7.13" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451" - integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA== - "@types/qs@*", "@types/qs@^6.9.5": version "6.9.17" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a" @@ -3511,13 +3569,19 @@ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/react@>=16": - version "18.3.12" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60" - integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== + version "19.0.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.2.tgz#9363e6b3ef898c471cb182dd269decc4afc1b4f6" + integrity sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg== dependencies: - "@types/prop-types" "*" csstype "^3.0.2" +"@types/readdir-glob@*": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a" + integrity sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg== + dependencies: + "@types/node" "*" + "@types/semver@^7.3.4", "@types/semver@^7.5.8": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" @@ -3545,6 +3609,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/tmp@^0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.6.tgz#d785ee90c52d7cc020e249c948c36f7b32d1e217" + integrity sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA== + "@types/unist@^2.0.0": version "2.0.11" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" @@ -3580,46 +3649,46 @@ "@types/node" "*" "@typescript-eslint/eslint-plugin@^8.11.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz#c95c6521e70c8b095a684d884d96c0c1c63747d2" - integrity sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg== + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz#992e5ac1553ce20d0d46aa6eccd79dc36dedc805" + integrity sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/type-utils" "8.15.0" - "@typescript-eslint/utils" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/scope-manager" "8.18.1" + "@typescript-eslint/type-utils" "8.18.1" + "@typescript-eslint/utils" "8.18.1" + "@typescript-eslint/visitor-keys" "8.18.1" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" "@typescript-eslint/parser@^8.11.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.15.0.tgz#92610da2b3af702cfbc02a46e2a2daa6260a9045" - integrity sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A== - dependencies: - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/typescript-estree" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.18.1.tgz#c258bae062778b7696793bc492249027a39dfb95" + integrity sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA== + dependencies: + "@typescript-eslint/scope-manager" "8.18.1" + "@typescript-eslint/types" "8.18.1" + "@typescript-eslint/typescript-estree" "8.18.1" + "@typescript-eslint/visitor-keys" "8.18.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz#28a1a0f13038f382424f45a988961acaca38f7c6" - integrity sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA== +"@typescript-eslint/scope-manager@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz#52cedc3a8178d7464a70beffed3203678648e55b" + integrity sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ== dependencies: - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/types" "8.18.1" + "@typescript-eslint/visitor-keys" "8.18.1" -"@typescript-eslint/type-utils@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz#a6da0f93aef879a68cc66c73fe42256cb7426c72" - integrity sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw== +"@typescript-eslint/type-utils@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz#10f41285475c0bdee452b79ff7223f0e43a7781e" + integrity sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ== dependencies: - "@typescript-eslint/typescript-estree" "8.15.0" - "@typescript-eslint/utils" "8.15.0" + "@typescript-eslint/typescript-estree" "8.18.1" + "@typescript-eslint/utils" "8.18.1" debug "^4.3.4" ts-api-utils "^1.3.0" @@ -3628,18 +3697,18 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9" integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ== -"@typescript-eslint/types@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.15.0.tgz#4958edf3d83e97f77005f794452e595aaf6430fc" - integrity sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ== +"@typescript-eslint/types@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.18.1.tgz#d7f4f94d0bba9ebd088de840266fcd45408a8fff" + integrity sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw== -"@typescript-eslint/typescript-estree@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz#915c94e387892b114a2a2cc0df2d7f19412c8ba7" - integrity sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg== +"@typescript-eslint/typescript-estree@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz#2a86cd64b211a742f78dfa7e6f4860413475367e" + integrity sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg== dependencies: - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/visitor-keys" "8.15.0" + "@typescript-eslint/types" "8.18.1" + "@typescript-eslint/visitor-keys" "8.18.1" debug "^4.3.4" fast-glob "^3.3.2" is-glob "^4.0.3" @@ -3661,15 +3730,15 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.15.0.tgz#ac04679ad19252776b38b81954b8e5a65567cef6" - integrity sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ== +"@typescript-eslint/utils@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.18.1.tgz#c4199ea23fc823c736e2c96fd07b1f7235fa92d5" + integrity sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "8.15.0" - "@typescript-eslint/types" "8.15.0" - "@typescript-eslint/typescript-estree" "8.15.0" + "@typescript-eslint/scope-manager" "8.18.1" + "@typescript-eslint/types" "8.18.1" + "@typescript-eslint/typescript-estree" "8.18.1" "@typescript-eslint/visitor-keys@7.18.0": version "7.18.0" @@ -3679,18 +3748,18 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" -"@typescript-eslint/visitor-keys@8.15.0": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz#9ea5a85eb25401d2aa74ec8a478af4e97899ea12" - integrity sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q== +"@typescript-eslint/visitor-keys@8.18.1": + version "8.18.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz#344b4f6bc83f104f514676facf3129260df7610a" + integrity sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ== dependencies: - "@typescript-eslint/types" "8.15.0" + "@typescript-eslint/types" "8.18.1" eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.1.tgz#28fa185f67daaf7b7a1a8c1d445132c5d979f8bd" + integrity sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA== "@vitejs/plugin-react@^3.0.1": version "3.1.0" @@ -3704,13 +3773,13 @@ react-refresh "^0.14.0" "@vitejs/plugin-react@^4.2.1": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz#28301ac6d7aaf20b73a418ee5c65b05519b4836c" - integrity sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA== + version "4.3.4" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz#c64be10b54c4640135a5b28a2432330e88ad7c20" + integrity sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug== dependencies: - "@babel/core" "^7.25.2" - "@babel/plugin-transform-react-jsx-self" "^7.24.7" - "@babel/plugin-transform-react-jsx-source" "^7.24.7" + "@babel/core" "^7.26.0" + "@babel/plugin-transform-react-jsx-self" "^7.25.9" + "@babel/plugin-transform-react-jsx-source" "^7.25.9" "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" @@ -3841,12 +3910,10 @@ agent-base@5: resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" - integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== - dependencies: - debug "^4.3.4" +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== aggregate-error@^3.0.0: version "3.1.0" @@ -4007,6 +4074,13 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-hidden@^1.1.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522" + integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== + dependencies: + tslib "^2.0.0" + aria-query@5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" @@ -4057,38 +4131,37 @@ array.prototype.findlastindex@^1.2.5: es-shim-unscopables "^1.0.2" array.prototype.flat@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== dependencies: array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" + call-bind "^1.0.8" define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" asn1@~0.2.3: version "0.2.6" @@ -4171,9 +4244,9 @@ aws4@^1.8.0: integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== axios@^1.2.2, axios@^1.3.5, axios@^1.6.8, axios@^1.7.2: - version "1.7.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" - integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -4446,13 +4519,13 @@ browserify-zlib@^0.1.4: pako "~0.2.0" browserslist@^4.24.0, browserslist@^4.24.2: - version "4.24.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" - integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + version "4.24.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2" + integrity sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA== dependencies: - caniuse-lite "^1.0.30001669" - electron-to-chromium "^1.5.41" - node-releases "^2.0.18" + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" update-browserslist-db "^1.1.1" bs-logger@^0.2.6: @@ -4536,16 +4609,31 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" + +call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" get-intrinsic "^1.2.4" - set-function-length "^1.2.1" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" + integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== + dependencies: + call-bind-apply-helpers "^1.0.1" + get-intrinsic "^1.2.6" callsites@^3.0.0: version "3.1.0" @@ -4562,10 +4650,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001669: - version "1.0.30001680" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" - integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== +caniuse-lite@^1.0.30001688: + version "1.0.30001690" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz#f2d15e3aaf8e18f76b2b8c1481abde063b8104c8" + integrity sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w== caseless@~0.12.0: version "0.12.0" @@ -4590,9 +4678,9 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: supports-color "^7.1.0" chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + version "5.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.0.tgz#846fdb5d5d939d6fa3d565cd5545697b6f8b6923" + integrity sha512-ZkD35Mx92acjB2yNJgziGqT9oKHEOxjTBTDRpOsRWtdecL/0jM3z5kM/CTzHWvHIen1GvkM85p6TuFfDGfc8/Q== char-regex@^1.0.2: version "1.0.2" @@ -4652,11 +4740,11 @@ cjs-module-lexer@^1.0.0: integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== class-variance-authority@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" - integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + version "0.7.1" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz#4008a798a0e4553a781a57ac5177c9fb5d043787" + integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== dependencies: - clsx "2.0.0" + clsx "^2.1.1" clean-stack@^2.0.0: version "2.2.0" @@ -4758,11 +4846,6 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== -clsx@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" - integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== - clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" @@ -5076,11 +5159,11 @@ data-view-byte-length@^1.0.1: is-data-view "^1.0.1" data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== dependencies: - call-bind "^1.0.6" + call-bound "^1.0.2" es-errors "^1.3.0" is-data-view "^1.0.1" @@ -5097,9 +5180,9 @@ debug@2.6.9, debug@^2.6.9: ms "2.0.0" debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: ms "^2.1.3" @@ -5205,7 +5288,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -5283,6 +5366,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-package-manager@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-package-manager/-/detect-package-manager-2.0.1.tgz#6b182e3ae5e1826752bfef1de9a7b828cffa50d8" @@ -5450,15 +5538,24 @@ dotenv-expand@^10.0.0: integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== dotenv@^16.0.0, dotenv@^16.3.1: - version "16.4.5" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" - integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== dotenv@^8.2.0: version "8.6.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer3@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" @@ -5499,10 +5596,10 @@ ejs@^3.1.10, ejs@^3.1.8: dependencies: jake "^10.8.5" -electron-to-chromium@^1.5.41: - version "1.5.63" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.63.tgz#69444d592fbbe628d129866c2355691ea93eda3e" - integrity sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA== +electron-to-chromium@^1.5.73: + version "1.5.74" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz#cb886b504a6467e4c00bea3317edb38393c53413" + integrity sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw== emittery@^0.13.1: version "0.13.1" @@ -5574,66 +5671,66 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.5.tgz#f4599a4946d57ed467515ed10e4f157289cd52fb" - integrity sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ== +es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.6: + version "1.23.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.6.tgz#55f0e1ce7128995cc04ace0a57d7dca348345108" + integrity sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA== dependencies: array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" + arraybuffer.prototype.slice "^1.0.4" available-typed-arrays "^1.0.7" - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" data-view-buffer "^1.0.1" data-view-byte-length "^1.0.1" data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" + es-define-property "^1.0.1" es-errors "^1.3.0" es-object-atoms "^1.0.0" es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.7" + get-intrinsic "^1.2.6" get-symbol-description "^1.0.2" globalthis "^1.0.4" - gopd "^1.0.1" + gopd "^1.2.0" has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" + has-proto "^1.2.0" + has-symbols "^1.1.0" hasown "^2.0.2" - internal-slot "^1.0.7" + internal-slot "^1.1.0" is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-data-view "^1.0.1" + is-data-view "^1.0.2" is-negative-zero "^2.0.3" - is-regex "^1.1.4" + is-regex "^1.2.1" is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" + is-string "^1.1.1" is-typed-array "^1.1.13" - is-weakref "^1.0.2" + is-weakref "^1.1.0" + math-intrinsics "^1.0.0" object-inspect "^1.13.3" object-keys "^1.1.1" object.assign "^4.1.5" regexp.prototype.flags "^1.5.3" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" + safe-array-concat "^1.1.3" + safe-regex-test "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" string.prototype.trimstart "^1.0.8" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" + typed-array-byte-offset "^1.0.3" + typed-array-length "^1.0.7" unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" + which-typed-array "^1.1.16" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== -es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -5674,21 +5771,21 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" -es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: +es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== dependencies: hasown "^2.0.0" -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" es6-promise@^4.2.4: version "4.2.8" @@ -6176,9 +6273,9 @@ expect@^29.0.0, expect@^29.7.0: jest-util "^29.7.0" express@^4.17.1, express@^4.17.3, express@^4.18.2: - version "4.21.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281" - integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ== + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== dependencies: accepts "~1.3.8" array-flatten "1.1.1" @@ -6199,7 +6296,7 @@ express@^4.17.1, express@^4.17.3, express@^4.18.2: methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.10" + path-to-regexp "0.1.12" proxy-addr "~2.0.7" qs "6.13.0" range-parser "~1.2.1" @@ -6476,9 +6573,9 @@ flatted@^3.2.9: integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA== flow-parser@0.*: - version "0.254.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.254.1.tgz#914a3420506c0dbd7a3be73d94c51ca41f2a7727" - integrity sha512-dyUrQD6ZyI4PVQppj8PP5kj6BVThK8FprAcPCnJNLIZM7zcAL6/xGS1EE1haWv2HcO/dHy7GSwOAO59uaffjYw== + version "0.257.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.257.0.tgz#7bac8843ca43c7ba46611dfdef4acb0828351a02" + integrity sha512-j1odE5mnPe6GOd5W1H/i8EXJvkhVquZCdoFVsZxUW8Yzda0OvjISCBBhDMjxtSkI1YU3d15BkTEeSYb5TLsVuw== follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.9" @@ -6622,15 +6719,16 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== +function.prototype.name@^1.1.6, function.prototype.name@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.7.tgz#9df48ea5f746bf577d7e15b5da89df8952a98e7b" + integrity sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.8" + define-properties "^1.2.1" functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" functions-have-names@^1.2.3: version "1.2.3" @@ -6655,16 +6753,26 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.1.3, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.6.tgz#43dd3dd0e7b49b82b2dfcad10dc824bf7fc265d5" + integrity sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA== dependencies: + call-bind-apply-helpers "^1.0.1" + dunder-proto "^1.0.0" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.0.0" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.0.0" + +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== get-npm-tarball-url@^2.0.3: version "2.1.0" @@ -6711,13 +6819,13 @@ get-stream@^8.0.1: integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== dependencies: - call-bind "^1.0.5" + call-bound "^1.0.3" es-errors "^1.3.0" - get-intrinsic "^1.2.4" + get-intrinsic "^1.2.6" getpass@^0.1.1: version "0.1.7" @@ -6869,12 +6977,10 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== got@^9.6.0: version "9.6.0" @@ -6940,10 +7046,10 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== has-flag@^3.0.0: version "3.0.0" @@ -6962,15 +7068,17 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1, has-proto@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" @@ -7087,11 +7195,11 @@ https-proxy-agent@^4.0.0: debug "4" https-proxy-agent@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" - integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: - agent-base "^7.0.2" + agent-base "^7.1.2" debug "4" human-signals@^1.1.1: @@ -7223,14 +7331,14 @@ inquirer@8.2.0: strip-ansi "^6.0.0" through "^2.3.6" -internal-slot@^1.0.4, internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== dependencies: es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" + hasown "^2.0.2" + side-channel "^1.1.0" ip@^2.0.0: version "2.0.1" @@ -7248,32 +7356,40 @@ is-absolute-url@^3.0.0: integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== is-arguments@^1.0.4, is-arguments@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" + integrity sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: - has-bigints "^1.0.1" + has-bigints "^1.0.2" is-binary-path@~2.1.0: version "2.1.0" @@ -7282,15 +7398,15 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== +is-boolean-object@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.1.tgz#c20d0c654be05da4fbc23c562635c019e93daf89" + integrity sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: +is-callable@^1.1.3, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -7302,26 +7418,29 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.13.0, is-core-module@^2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== +is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.0.tgz#6c01ffdd5e33c49c1d2abfa93334a85cb56bd81c" + integrity sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g== dependencies: hasown "^2.0.2" -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" is-typed-array "^1.1.13" -is-date-object@^1.0.1, is-date-object@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + has-tostringtag "^1.0.2" is-deflate@^1.0.0: version "1.0.0" @@ -7338,6 +7457,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -7353,7 +7479,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.7: +is-generator-function@^1.0.10, is-generator-function@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== @@ -7400,12 +7526,13 @@ is-npm@^5.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8" integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + has-tostringtag "^1.0.2" is-number@^7.0.0: version "7.0.0" @@ -7454,13 +7581,15 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-regex@^1.1.2, is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== +is-regex@^1.1.2, is-regex@^1.1.4, is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" is-regexp@^1.0.0: version "1.0.0" @@ -7473,11 +7602,11 @@ is-set@^2.0.2, is-set@^2.0.3: integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" is-stream@^2.0.0, is-stream@^2.0.1: version "2.0.1" @@ -7489,26 +7618,29 @@ is-stream@^3.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== +is-string@^1.0.7, is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== dependencies: - has-tostringtag "^1.0.0" + call-bound "^1.0.3" + has-tostringtag "^1.0.2" -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== +is-symbol@^1.0.3, is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== dependencies: - has-symbols "^1.0.2" + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" -is-typed-array@^1.1.13, is-typed-array@^1.1.3: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15, is-typed-array@^1.1.3: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== dependencies: - which-typed-array "^1.1.14" + which-typed-array "^1.1.16" is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" @@ -7535,20 +7667,20 @@ is-weakmap@^2.0.2: resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== +is-weakref@^1.0.2, is-weakref@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.0.tgz#47e3472ae95a63fa9cf25660bcf0c181c39770ef" + integrity sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.2" is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" is-windows@^1.0.1: version "1.0.2" @@ -8104,7 +8236,12 @@ jsdom@^25.0.1: ws "^8.18.0" xml-name-validator "^5.0.0" -jsesc@^3.0.2, jsesc@~3.0.2: +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +jsesc@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== @@ -8427,9 +8564,9 @@ magic-string@^0.27.0: "@jridgewell/sourcemap-codec" "^1.4.13" magic-string@^0.30.0, magic-string@^0.30.11: - version "0.30.13" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.13.tgz#92438e3ff4946cf54f18247c981e5c161c46683c" - integrity sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g== + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== dependencies: "@jridgewell/sourcemap-codec" "^1.5.0" @@ -8473,9 +8610,14 @@ map-or-similar@^1.5.0: integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg== markdown-to-jsx@^7.1.8: - version "7.7.0" - resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.7.0.tgz#98424780af4b9cccd55873c5bc7e43c0b00ff579" - integrity sha512-130nIMbJY+woOQJ11xTqEtYko60t6EpNkZuqjKMferL3udtob3nRfzXOdsiA26NPemiR7w/hR8M3/B9yiYPGZg== + version "7.7.2" + resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.7.2.tgz#59c1dd64f48b53719311ab140be3cd51cdabccd3" + integrity sha512-N3AKfYRvxNscvcIH6HDnDKILp4S8UWbebp+s92Y8SwIq0CuSbLW4Jgmrbjku3CWKjTQO0OyIMS6AhzqrwjEa3g== + +math-intrinsics@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== mdast-util-definitions@^4.0.0: version "4.0.0" @@ -8720,9 +8862,9 @@ mute-stream@^1.0.0: integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare@^1.4.0: version "1.4.0" @@ -8782,10 +8924,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" - integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== node-source-walk@^7.0.0: version "7.0.0" @@ -8836,9 +8978,9 @@ npm-run-path@^5.1.0: path-key "^4.0.0" nwsapi@^2.2.12: - version "2.2.13" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" - integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== + version "2.2.16" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" + integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== nypm@^0.3.8: version "0.3.12" @@ -8862,7 +9004,7 @@ object-assign@^4, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1, object-inspect@^1.13.3: +object-inspect@^1.13.3: version "1.13.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a" integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== @@ -8881,13 +9023,15 @@ object-keys@^1.1.1: integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.4, object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: - call-bind "^1.0.5" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" - has-symbols "^1.0.3" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" object-keys "^1.1.1" object.fromentries@^2.0.8: @@ -8910,11 +9054,12 @@ object.groupby@^1.0.3: es-abstract "^1.23.2" object.values@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" define-properties "^1.2.1" es-object-atoms "^1.0.0" @@ -9179,10 +9324,10 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" - integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== path-type@^4.0.0: version "4.0.0" @@ -9341,20 +9486,15 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== -prettier@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== - prettier@^2.8.0: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== -prettier@^3.3.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" - integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== +prettier@^3.3.0, prettier@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== pretty-format@^27.0.2: version "27.5.1" @@ -9432,9 +9572,9 @@ proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== psl@^1.1.28: - version "1.12.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.12.0.tgz#8b09cba186ebee68c0d824c1b22944cf5b2ad42c" - integrity sha512-OVcqwt4qWJF9G0fnSEMNz7aSa1PiGX/IvSXDO+PpbDK3r/IJ3QX2iu8ywanYG07e9IaYDigMu+EapE8TdMZOJQ== + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== dependencies: punycode "^2.3.1" @@ -9645,6 +9785,33 @@ react-refresh@^0.14.0, react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== +react-remove-scroll-bar@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223" + integrity sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== + dependencies: + react-style-singleton "^2.2.2" + tslib "^2.0.0" + +react-remove-scroll@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz#2518d2c5112e71ea8928f1082a58459b5c7a2a97" + integrity sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw== + dependencies: + react-remove-scroll-bar "^2.3.7" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.3" + use-sidecar "^1.1.2" + +react-style-singleton@^2.2.1, react-style-singleton@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz#4265608be69a4d70cfe3047f2c6c88b2c3ace388" + integrity sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== + dependencies: + get-nonce "^1.0.0" + tslib "^2.0.0" + react@18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -9694,9 +9861,9 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: util-deprecate "^1.0.1" readable-stream@^4.0.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" - integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + version "4.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.6.0.tgz#ce412dfb19c04efde1c5936d99c27f37a1ff94c9" + integrity sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" @@ -9739,6 +9906,20 @@ recast@^0.23.1: tiny-invariant "^1.3.3" tslib "^2.0.1" +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz#c905f3386008de95a62315f3ea8630404be19e2f" + integrity sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + dunder-proto "^1.0.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + which-builtin-type "^1.2.1" + regenerate-unicode-properties@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" @@ -9778,7 +9959,7 @@ regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.3: es-errors "^1.3.0" set-function-name "^2.0.2" -regexpu-core@^6.1.1: +regexpu-core@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== @@ -9926,16 +10107,16 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== resolve@^1.10.0, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.4, resolve@^1.22.8: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== dependencies: - is-core-module "^2.13.0" + is-core-module "^2.16.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -9993,30 +10174,31 @@ rimraf@~2.6.2: fsevents "~2.3.2" rollup@^4.20.0: - version "4.27.3" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.27.3.tgz#078ecb20830c1de1f5486607f3e2f490269fb98a" - integrity sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ== + version "4.28.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.1.tgz#7718ba34d62b449dfc49adbfd2f312b4fe0df4de" + integrity sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg== dependencies: "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.27.3" - "@rollup/rollup-android-arm64" "4.27.3" - "@rollup/rollup-darwin-arm64" "4.27.3" - "@rollup/rollup-darwin-x64" "4.27.3" - "@rollup/rollup-freebsd-arm64" "4.27.3" - "@rollup/rollup-freebsd-x64" "4.27.3" - "@rollup/rollup-linux-arm-gnueabihf" "4.27.3" - "@rollup/rollup-linux-arm-musleabihf" "4.27.3" - "@rollup/rollup-linux-arm64-gnu" "4.27.3" - "@rollup/rollup-linux-arm64-musl" "4.27.3" - "@rollup/rollup-linux-powerpc64le-gnu" "4.27.3" - "@rollup/rollup-linux-riscv64-gnu" "4.27.3" - "@rollup/rollup-linux-s390x-gnu" "4.27.3" - "@rollup/rollup-linux-x64-gnu" "4.27.3" - "@rollup/rollup-linux-x64-musl" "4.27.3" - "@rollup/rollup-win32-arm64-msvc" "4.27.3" - "@rollup/rollup-win32-ia32-msvc" "4.27.3" - "@rollup/rollup-win32-x64-msvc" "4.27.3" + "@rollup/rollup-android-arm-eabi" "4.28.1" + "@rollup/rollup-android-arm64" "4.28.1" + "@rollup/rollup-darwin-arm64" "4.28.1" + "@rollup/rollup-darwin-x64" "4.28.1" + "@rollup/rollup-freebsd-arm64" "4.28.1" + "@rollup/rollup-freebsd-x64" "4.28.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.28.1" + "@rollup/rollup-linux-arm-musleabihf" "4.28.1" + "@rollup/rollup-linux-arm64-gnu" "4.28.1" + "@rollup/rollup-linux-arm64-musl" "4.28.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.28.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.28.1" + "@rollup/rollup-linux-riscv64-gnu" "4.28.1" + "@rollup/rollup-linux-s390x-gnu" "4.28.1" + "@rollup/rollup-linux-x64-gnu" "4.28.1" + "@rollup/rollup-linux-x64-musl" "4.28.1" + "@rollup/rollup-win32-arm64-msvc" "4.28.1" + "@rollup/rollup-win32-ia32-msvc" "4.28.1" + "@rollup/rollup-win32-x64-msvc" "4.28.1" fsevents "~2.3.2" rrweb-cssom@^0.7.1: @@ -10043,14 +10225,15 @@ rxjs@^7.2.0, rxjs@^7.5.1: dependencies: tslib "^2.1.0" -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" isarray "^2.0.5" safe-buffer@5.1.1: @@ -10068,14 +10251,14 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== dependencies: - call-bind "^1.0.6" + call-bound "^1.0.2" es-errors "^1.3.0" - is-regex "^1.1.4" + is-regex "^1.2.1" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" @@ -10180,7 +10363,7 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" -set-function-length@^1.2.1: +set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== @@ -10226,15 +10409,45 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" @@ -10380,11 +10593,12 @@ statuses@2.0.1: integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== stop-iteration-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" - integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== dependencies: - internal-slot "^1.0.4" + es-errors "^1.3.0" + internal-slot "^1.1.0" store2@^2.12.0, store2@^2.14.2: version "2.14.3" @@ -10404,9 +10618,9 @@ stream-to-array@^2.3.0: any-promise "^1.1.0" streamx@^2.15.0: - version "2.20.2" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.2.tgz#6a8911959d6f307c19781a1d19ecd94b5f042d78" - integrity sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA== + version "2.21.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.1.tgz#f02979d8395b6b637d08a589fb514498bed55845" + integrity sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw== dependencies: fast-fifo "^1.3.2" queue-tick "^1.0.1" @@ -10445,22 +10659,26 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" define-properties "^1.2.1" - es-abstract "^1.23.0" + es-abstract "^1.23.5" es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== +string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.2" define-properties "^1.2.1" es-object-atoms "^1.0.0" @@ -10605,9 +10823,9 @@ synchronous-promise@^2.0.15: integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== table@^6.6.0, table@^6.8.1: - version "6.8.2" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" - integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + version "6.9.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.9.0.tgz#50040afa6264141c7566b3b81d4d82c47a8668f5" + integrity sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -10616,9 +10834,9 @@ table@^6.6.0, table@^6.8.1: strip-ansi "^6.0.1" tailwind-merge@^2.5.4: - version "2.5.4" - resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.4.tgz#4bf574e81fa061adeceba099ae4df56edcee78d1" - integrity sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q== + version "2.5.5" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.5.tgz#98167859b856a2a6b8d2baf038ee171b9d814e39" + integrity sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA== tailwindcss-animate@^1.0.7: version "1.0.7" @@ -10726,9 +10944,11 @@ test-exclude@^6.0.0: minimatch "^3.0.4" text-decoder@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.1.tgz#e173f5121d97bfa3ff8723429ad5ba92e1ead67e" - integrity sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ== + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" text-table@^0.2.0: version "0.2.0" @@ -10753,17 +10973,17 @@ tiny-invariant@^1.3.1, tiny-invariant@^1.3.3: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== -tldts-core@^6.1.62: - version "6.1.62" - resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.62.tgz#4568182465aa84dd14404ccbae0e8d82364ed25d" - integrity sha512-ohONqbfobpuaylhqFbtCzc0dFFeNz85FVKSesgT8DS9OV3a25Yj730pTj7/dDtCqmgoCgEj6gDiU9XxgHKQlBw== +tldts-core@^6.1.69: + version "6.1.69" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.69.tgz#079ffcac8a4407bc74567e292aecf30b943674e1" + integrity sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA== tldts@^6.1.32: - version "6.1.62" - resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.62.tgz#f66eaabce34287faf4d9ef7111781226aa001dbd" - integrity sha512-TF+wo3MgTLbf37keEwQD0IxvOZO8UZxnpPJDg5iFGAASGxYzbX/Q0y944ATEjrfxG/pF1TWRHCPbFp49Mz1Y1w== + version "6.1.69" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.69.tgz#0fe1fcb1ad09510459693e72f96062cee2411f1f" + integrity sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw== dependencies: - tldts-core "^6.1.62" + tldts-core "^6.1.69" tmp@^0.0.33: version "0.0.33" @@ -10837,9 +11057,9 @@ tr46@~0.0.3: integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== ts-api-utils@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.0.tgz#709c6f2076e511a81557f3d07a0cbd566ae8195c" - integrity sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-dedent@^2.0.0, ts-dedent@^2.2.0: version "2.2.0" @@ -10847,14 +11067,14 @@ ts-dedent@^2.0.0, ts-dedent@^2.2.0: integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== ts-graphviz@^2.1.2: - version "2.1.4" - resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-2.1.4.tgz#16db94dc83a7a7b5af6f5b113cd26c0c56c7f9f8" - integrity sha512-0g465/ES70H0h5rcLUqaenKqNYekQaR9W0m0xUGy3FxueGujpGr+0GN2YWlgLIYSE2Xg0W7Uq1Qqnn7Cg+Af2w== + version "2.1.5" + resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-2.1.5.tgz#8b050f5b0632e91eee4225dfece6e4df21c375f4" + integrity sha512-IigMCo40QZvyyURRdYFh0DV6DGDt7OqkPM/TBGXSJKfNKnYmOfRg0tzSlnJS1TQCWFSTEtpBQsqmAZcziXJrWg== dependencies: - "@ts-graphviz/adapter" "^2.0.5" - "@ts-graphviz/ast" "^2.0.5" - "@ts-graphviz/common" "^2.1.4" - "@ts-graphviz/core" "^2.0.5" + "@ts-graphviz/adapter" "^2.0.6" + "@ts-graphviz/ast" "^2.0.6" + "@ts-graphviz/common" "^2.1.5" + "@ts-graphviz/core" "^2.0.6" ts-jest@^29.2.5: version "29.2.5" @@ -10914,7 +11134,7 @@ tslib@^1.13.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -10979,9 +11199,9 @@ type-fest@^3.11.0, type-fest@^3.8.0: integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g== type-fest@^4.18.2: - version "4.28.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.28.0.tgz#ee4b007cbd1db07aed98b19faa0a2852a8006162" - integrity sha512-jXMwges/FVbFRe5lTMJZVEZCrO9kI9c8k0PA/z7nF3bo0JSCCLysvokFjNPIUK/itEMas10MQM+AiHoHt/T/XA== + version "4.30.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.30.2.tgz#d94429edde1f7deacf554741650aab394197a4cc" + integrity sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig== type-is@~1.6.18: version "1.6.18" @@ -10992,48 +11212,49 @@ type-is@~1.6.18: mime-types "~2.1.24" typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.3" es-errors "^1.3.0" - is-typed-array "^1.1.13" + is-typed-array "^1.1.14" typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== dependencies: - call-bind "^1.0.7" + call-bind "^1.0.8" for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== +typed-array-byte-offset@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== dependencies: available-typed-arrays "^1.0.7" - call-bind "^1.0.7" + call-bind "^1.0.8" for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== dependencies: call-bind "^1.0.7" for-each "^0.3.3" gopd "^1.0.1" - has-proto "^1.0.3" is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" typedarray-to-buffer@^3.1.5: version "3.1.5" @@ -11048,9 +11269,9 @@ typedarray@^0.0.6: integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== typescript@^5.4.4, typescript@^5.4.5, typescript@^5.5.4, typescript@^5.6.2: - version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" - integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== + version "5.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== typescript@~4.7.0: version "4.7.4" @@ -11073,19 +11294,19 @@ uglify-js@^3.1.4: integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== dependencies: - call-bind "^1.0.2" + call-bound "^1.0.3" has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" -undici-types@~6.19.8: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" @@ -11229,6 +11450,13 @@ url-parse@^1.4.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-callback-ref@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz#98d9fab067075841c5b2c6852090d5d0feabe2bf" + integrity sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== + dependencies: + tslib "^2.0.0" + use-resize-observer@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c" @@ -11236,6 +11464,14 @@ use-resize-observer@^9.1.0: dependencies: "@juggle/resize-observer" "^3.3.1" +use-sidecar@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.3.tgz#10e7fd897d130b896e2c546c63a5e8233d00efdb" + integrity sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -11413,9 +11649,9 @@ whatwg-mimetype@^4.0.0: integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== whatwg-url@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" - integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + version "14.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.1.0.tgz#fffebec86cc8e6c2a657e50dc606207b870f0ab3" + integrity sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w== dependencies: tr46 "^5.0.0" webidl-conversions "^7.0.0" @@ -11428,18 +11664,37 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== +which-boxed-primitive@^1.0.2, which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" -which-collection@^1.0.1: +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.1, which-collection@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== @@ -11454,15 +11709,16 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz#35ccf7b1a0fce87bd8b92a478c9d045785d3bf35" integrity sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA== -which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== +which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.2: + version "1.1.18" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" + integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA== dependencies: available-typed-arrays "^1.0.7" - call-bind "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.3" for-each "^0.3.3" - gopd "^1.0.1" + gopd "^1.2.0" has-tostringtag "^1.0.2" which@^1.2.14: @@ -11497,7 +11753,6 @@ 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==