diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index 15c130891..7ee06054d 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -5,13 +5,13 @@ "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { - "build": "pnpm compile && oclif manifest", - "clean": "rm -rf ./lib ./node_modules tsconfig.tsbuildinfo", + "build": "npm run clean && npm run compile", + "clean": "rm -rf ./lib ./node_modules tsconfig.build.tsbuildinfo", "compile": "tsc -b tsconfig.json", "postpack": "rm -f oclif.manifest.json", "prepack": "pnpm compile && oclif manifest && oclif readme", "version": "oclif readme && git add README.md", - "test": "npm run build && npm run test:e2e", + "test": "npm run compile && npm run test:e2e", "test:e2e": "nyc mocha \"test/**/*.test.js\" || exit 0", "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, @@ -22,7 +22,7 @@ "@contentstack/cli-config": "~2.0.0-beta.3", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.37", - "inquirer": "8.2.7", + "inquirer": "12.11.1", "mkdirp": "^1.0.4", "tar": "^7.5.7" }, @@ -30,7 +30,7 @@ "@oclif/test": "^4.1.13", "@types/inquirer": "^9.0.8", "@types/mkdirp": "^1.0.2", - "@types/node": "^14.18.63", + "@types/node": "^18.11.9", "@types/tar": "^6.1.13", "chai": "^4.5.0", "eslint": "^8.57.1", @@ -41,7 +41,7 @@ "oclif": "^4.17.46", "tmp": "^0.2.5", "ts-node": "^8.10.2", - "typescript": "^4.9.5" + "typescript": "^5.9.3" }, "engines": { "node": ">=14.0.0" diff --git a/packages/contentstack-bootstrap/src/bootstrap/interactive.ts b/packages/contentstack-bootstrap/src/bootstrap/interactive.ts index 2db5d0e55..2f74ea18a 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/interactive.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/interactive.ts @@ -1,4 +1,4 @@ -const inquirer = require('inquirer'); +import inquirer from 'inquirer'; import { cliux, pathValidator } from '@contentstack/cli-utilities'; import messageHandler from '../messages'; @@ -50,15 +50,14 @@ export async function inquireCloneDirectory(): Promise { } // Ask for the custom path - let selectedCustomPath = await inquirer.prompt([ + const selectedCustomPath = await inquirer.prompt([ { - type: 'string', + type: 'input', name: 'path', message: messageHandler.parse('CLI_BOOTSTRAP_APP_COPY_SOURCE_CODE_DESTINATION_ENQUIRY'), }, ]); - selectedCustomPath = pathValidator(selectedCustomPath.path); - return selectedCustomPath; + return pathValidator(selectedCustomPath.path); } export async function inquireGithubAccessToken(): Promise { diff --git a/packages/contentstack-bootstrap/test/bootstrap.test.js b/packages/contentstack-bootstrap/test/bootstrap.test.js index e69c96372..d0c2730d1 100644 --- a/packages/contentstack-bootstrap/test/bootstrap.test.js +++ b/packages/contentstack-bootstrap/test/bootstrap.test.js @@ -75,7 +75,7 @@ describe('Bootstrapping an app', () => { configHandlerTokens = { 'test-alias': { token: aliasToken } }, } = options; - // configHandler stub + // configHandler stub (tokens only when hasAlias; do not stub get for auth - use configHandler.set('authorisationType', 'OAUTH') in tests instead) if (hasAlias) { sandbox.stub(configHandler, 'get').withArgs('tokens').returns(configHandlerTokens); } @@ -371,9 +371,8 @@ describe('Bootstrapping an app', () => { }, }); - // Mock region and cmaHost - command.region = mock.region; - command.cmaHost = mock.region.cma; + // Mock region and cmaHost (base class getter uses _region, then cmaHost uses region) + command._region = mock.region; // Mock managementSDKClient const managementAPIClientStub = { @@ -408,6 +407,7 @@ describe('Bootstrapping an app', () => { sandbox.restore(); sandbox = sinon.createSandbox(); setupStubs(); + configHandler.set('authorisationType', 'OAUTH'); const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; const command = new BootstrapCommand([], {}); @@ -417,6 +417,8 @@ describe('Bootstrapping an app', () => { sandbox.stub(interactive, 'inquireAppType').resolves('starterapp'); sandbox.stub(interactive, 'inquireApp').resolves(mock.appConfig); sandbox.stub(interactive, 'inquireCloneDirectory').resolves('/test/path'); + sandbox.stub(interactive, 'inquireLivePreviewSupport'); + sandbox.stub(interactive, 'inquireRunDevServer'); // Mock config const config = require('../lib/config'); @@ -462,6 +464,7 @@ describe('Bootstrapping an app', () => { sandbox.restore(); sandbox = sinon.createSandbox(); setupStubs(); + configHandler.set('authorisationType', 'OAUTH'); const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; const command = new BootstrapCommand([], {}); @@ -471,6 +474,8 @@ describe('Bootstrapping an app', () => { sandbox.stub(interactive, 'inquireAppType').resolves('starterapp'); sandbox.stub(interactive, 'inquireApp').resolves(mock.appConfig); sandbox.stub(interactive, 'inquireCloneDirectory').resolves('/test/path'); + sandbox.stub(interactive, 'inquireLivePreviewSupport'); + sandbox.stub(interactive, 'inquireRunDevServer'); // Mock config const config = require('../lib/config'); @@ -516,6 +521,7 @@ describe('Bootstrapping an app', () => { sandbox.restore(); sandbox = sinon.createSandbox(); setupStubs(); + configHandler.set('authorisationType', 'OAUTH'); const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; const command = new BootstrapCommand([], {}); @@ -572,6 +578,7 @@ describe('Bootstrapping an app', () => { sandbox.restore(); sandbox = sinon.createSandbox(); setupStubs(); + configHandler.set('authorisationType', 'OAUTH'); const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; const command = new BootstrapCommand([], {}); @@ -619,21 +626,23 @@ describe('Bootstrapping an app', () => { // Verify that appType is set correctly expect(bootstrapOptions).to.not.be.null; expect(bootstrapOptions.appType).to.equal('sampleapp'); - // Verify that inquireApp was called with sampleApps - expect(interactive.inquireApp.calledWith(config.sampleApps)).to.be.true; + // Verify that inquireApp was called with sampleApps (config.default in compiled CJS) + expect(interactive.inquireApp.calledWith(config.default.sampleApps)).to.be.true; }); it('should handle app-name flag correctly', async () => { sandbox.restore(); sandbox = sinon.createSandbox(); setupStubs(); + configHandler.set('authorisationType', 'OAUTH'); const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; const command = new BootstrapCommand([], {}); - // Mock interactive functions + // Mock interactive functions (stub inquireApp so .called exists for assertion) const interactive = require('../lib/bootstrap/interactive'); sandbox.stub(interactive, 'inquireAppType').resolves('starterapp'); + sandbox.stub(interactive, 'inquireApp').resolves(mock.appConfig); sandbox.stub(interactive, 'inquireCloneDirectory').resolves('/test/path'); sandbox.stub(interactive, 'inquireLivePreviewSupport').resolves(false); sandbox.stub(interactive, 'inquireRunDevServer').resolves(false); diff --git a/packages/contentstack-bootstrap/test/interactive-dev-server.test.js b/packages/contentstack-bootstrap/test/interactive-dev-server.test.js index 9dbe58afe..07e5307d4 100644 --- a/packages/contentstack-bootstrap/test/interactive-dev-server.test.js +++ b/packages/contentstack-bootstrap/test/interactive-dev-server.test.js @@ -1,6 +1,9 @@ +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); const { expect } = require('chai'); const sinon = require('sinon'); -const inquirer = require('inquirer'); +// Inquirer v12 CJS export is { default: { prompt, ... } }; use default so stubs apply to what lib uses +const inquirer = require('inquirer').default || require('inquirer'); const { inquireRunDevServer } = require('../lib/bootstrap/interactive'); const messages = require('../messages/index.json'); diff --git a/packages/contentstack-bootstrap/test/interactive.test.js b/packages/contentstack-bootstrap/test/interactive.test.js index 6d5e70812..491078fc0 100644 --- a/packages/contentstack-bootstrap/test/interactive.test.js +++ b/packages/contentstack-bootstrap/test/interactive.test.js @@ -1,6 +1,9 @@ +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); const { expect } = require('chai'); const sinon = require('sinon'); -const inquirer = require('inquirer'); +// Inquirer v12 CJS export is { default: { prompt, ... } }; use default so stubs apply to what lib uses +const inquirer = require('inquirer').default || require('inquirer'); const { inquireApp, inquireCloneDirectory, @@ -11,6 +14,7 @@ const { continueBootstrapCommand, } = require('../lib/bootstrap/interactive'); const messages = require('../messages/index.json'); +const { pathValidator } = require('@contentstack/cli-utilities'); describe('Interactive Functions Tests', () => { let sandbox; @@ -101,30 +105,25 @@ describe('Interactive Functions Tests', () => { .resolves({ path: 'Other' }) .onSecondCall() .resolves({ path: customPath }); - const pathValidatorStub = sandbox.stub(require('@contentstack/cli-utilities'), 'pathValidator').returns(customPath); const result = await inquireCloneDirectory(); - expect(result).to.equal(customPath); + expect(result).to.equal(pathValidator(customPath)); expect(inquirer.prompt.calledTwice).to.be.true; - expect(pathValidatorStub.calledOnce).to.be.true; }); it('should validate custom path using pathValidator', async () => { const rawPath = '/some/path'; - const validatedPath = '/validated/path'; sandbox .stub(inquirer, 'prompt') .onFirstCall() .resolves({ path: 'Other' }) .onSecondCall() .resolves({ path: rawPath }); - const pathValidatorStub = sandbox.stub(require('@contentstack/cli-utilities'), 'pathValidator').returns(validatedPath); const result = await inquireCloneDirectory(); - expect(pathValidatorStub.calledWith(rawPath)).to.be.true; - expect(result).to.equal(validatedPath); + expect(result).to.equal(pathValidator(rawPath)); }); }); diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index 839ce17dc..524e48fdd 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -13,7 +13,7 @@ "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "chalk": "^4.1.2", - "inquirer": "8.2.7", + "inquirer": "12.11.1", "lodash": "^4.17.23", "merge": "^2.1.1", "ora": "^5.4.1", @@ -24,7 +24,7 @@ "@oclif/test": "^4.1.13", "@types/chai": "^4.3.0", "@types/mocha": "^10.0.0", - "@types/node": "^14.18.63", + "@types/node": "^18.11.9", "@types/sinon": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.62.0", "chai": "^4.5.0", @@ -35,7 +35,7 @@ "oclif": "^4.17.46", "sinon": "^21.0.1", "ts-node": "^10.9.2", - "typescript": "^4.9.5" + "typescript": "^5.9.3" }, "engines": { "node": ">=14.0.0" diff --git a/packages/contentstack-clone/src/core/util/clone-handler.ts b/packages/contentstack-clone/src/core/util/clone-handler.ts index b3ac29f5f..412d3bad1 100644 --- a/packages/contentstack-clone/src/core/util/clone-handler.ts +++ b/packages/contentstack-clone/src/core/util/clone-handler.ts @@ -170,8 +170,7 @@ export class CloneHandler { } displayBackOptionMessage(): void { - const ui = new inquirer.ui.BottomBar(); - ui.updateBottomBar(chalk.cyan('\nPress shift & left arrow together to undo the operation\n')); + process.stdout.write(chalk.cyan('\nPress shift & left arrow together to undo the operation\n')); } setBackKeyPressHandler(backKeyPressHandler: (...args: any[]) => void): void { diff --git a/packages/contentstack-clone/test/lib/util/clone-handler.commands.test.ts b/packages/contentstack-clone/test/lib/util/clone-handler.commands.test.ts index 46d67e81c..12b755581 100644 --- a/packages/contentstack-clone/test/lib/util/clone-handler.commands.test.ts +++ b/packages/contentstack-clone/test/lib/util/clone-handler.commands.test.ts @@ -399,11 +399,6 @@ describe('CloneHandler - Commands', () => { const configHandler = require('@contentstack/cli-utilities').configHandler; configHandlerGetStub = sandbox.stub(configHandler, 'get').returns(undefined); - // Stub inquirer.ui.BottomBar to prevent hanging in displayBackOptionMessage - sandbox.stub(inquirer.ui, 'BottomBar').returns({ - updateBottomBar: sandbox.stub(), - } as any); - // Stub ora spinner - following import plugin pattern const oraModule = require('ora'); const mockSpinner = { diff --git a/packages/contentstack-clone/test/lib/util/clone-handler.execution.test.ts b/packages/contentstack-clone/test/lib/util/clone-handler.execution.test.ts index 0d9ec083e..78a0cca3c 100644 --- a/packages/contentstack-clone/test/lib/util/clone-handler.execution.test.ts +++ b/packages/contentstack-clone/test/lib/util/clone-handler.execution.test.ts @@ -425,10 +425,6 @@ describe('CloneHandler - Execution', () => { stack: sandbox.stub(), }; handler.setClient(mockClient); - // Stub inquirer.ui.BottomBar to prevent hanging - sandbox.stub(inquirer.ui, 'BottomBar').returns({ - updateBottomBar: sandbox.stub(), - } as any); }); afterEach(() => { diff --git a/packages/contentstack-clone/test/lib/util/clone-handler.helpers.test.ts b/packages/contentstack-clone/test/lib/util/clone-handler.helpers.test.ts index e8c68405e..a33143b51 100644 --- a/packages/contentstack-clone/test/lib/util/clone-handler.helpers.test.ts +++ b/packages/contentstack-clone/test/lib/util/clone-handler.helpers.test.ts @@ -26,14 +26,11 @@ describe('CloneHandler - Helpers', () => { }); it('should display back option message', () => { - const uiStub = { - updateBottomBar: sandbox.stub(), - }; - sandbox.stub(inquirer.ui, 'BottomBar').returns(uiStub as any); - + const writeStub = sandbox.stub(process.stdout, 'write'); handler.displayBackOptionMessage(); - - expect(uiStub.updateBottomBar.calledOnce).to.be.true; + expect(writeStub.calledOnce).to.be.true; + expect(writeStub.firstCall.args[0]).to.include('Press shift & left arrow together to undo'); + writeStub.restore(); }); }); diff --git a/packages/contentstack-clone/test/lib/util/clone-handler.stack.test.ts b/packages/contentstack-clone/test/lib/util/clone-handler.stack.test.ts index cd6f71e5f..a639c3538 100644 --- a/packages/contentstack-clone/test/lib/util/clone-handler.stack.test.ts +++ b/packages/contentstack-clone/test/lib/util/clone-handler.stack.test.ts @@ -21,11 +21,6 @@ describe('CloneHandler - Stack', () => { const configHandler = require('@contentstack/cli-utilities').configHandler; configHandlerGetStub = sandbox.stub(configHandler, 'get').returns(undefined); - // Stub inquirer.ui.BottomBar to prevent hanging in displayBackOptionMessage - sandbox.stub(inquirer.ui, 'BottomBar').returns({ - updateBottomBar: sandbox.stub(), - } as any); - const config: CloneConfig = { cloneContext: { command: 'test', diff --git a/packages/contentstack-export-to-csv/package.json b/packages/contentstack-export-to-csv/package.json index 54221df58..0f66112af 100644 --- a/packages/contentstack-export-to-csv/package.json +++ b/packages/contentstack-export-to-csv/package.json @@ -9,9 +9,7 @@ "@contentstack/cli-utilities": "~2.0.0-beta.2", "@oclif/core": "^4.8.0", "@oclif/plugin-help": "^6.2.32", - "fast-csv": "^4.3.6", - "inquirer": "8.2.7", - "inquirer-checkbox-plus-prompt": "1.4.2" + "fast-csv": "^4.3.6" }, "devDependencies": { "@oclif/test": "^4.1.13", diff --git a/packages/contentstack-export-to-csv/src/utils/interactive.ts b/packages/contentstack-export-to-csv/src/utils/interactive.ts index ef9bd9976..f48fc35bd 100644 --- a/packages/contentstack-export-to-csv/src/utils/interactive.ts +++ b/packages/contentstack-export-to-csv/src/utils/interactive.ts @@ -3,9 +3,7 @@ * Migrated from: packages/contentstack-export-to-csv/src/util/index.js */ -import inquirer, { Answers } from 'inquirer'; -// @ts-ignore - no types available -import checkboxPlus from 'inquirer-checkbox-plus-prompt'; +import { select, checkbox, confirm } from '@inquirer/prompts'; import { cliux } from '@contentstack/cli-utilities'; import { messages } from '../messages'; @@ -24,9 +22,6 @@ import type { LanguageMap, } from '../types'; -// Register checkbox-plus prompt type -inquirer.registerPrompt('checkbox-plus', checkboxPlus); - // ============================================================================ // Startup Questions // ============================================================================ @@ -34,25 +29,20 @@ inquirer.registerPrompt('checkbox-plus', checkboxPlus); /** * Display startup questions to choose an action. */ -export function startupQuestions(): Promise { - return new Promise((resolve, reject) => { - const actions = [ - { - type: 'list', - name: 'action', - message: 'Choose Action', - choices: [messages.ACTION_EXPORT_ENTRIES, messages.ACTION_EXPORT_USERS, messages.ACTION_EXPORT_TEAMS, messages.ACTION_EXPORT_TAXONOMIES, 'Exit'], - }, - ]; - - inquirer - .prompt(actions) - .then((answers: Answers) => { - if (answers.action === 'Exit') exitProgram(); - resolve(answers.action as string); - }) - .catch(reject); +export async function startupQuestions(): Promise { + const action = await select({ + message: 'Choose Action', + choices: [ + { value: messages.ACTION_EXPORT_ENTRIES }, + { value: messages.ACTION_EXPORT_USERS }, + { value: messages.ACTION_EXPORT_TEAMS }, + { value: messages.ACTION_EXPORT_TAXONOMIES }, + { value: 'Exit' }, + ], }); + + if (action === 'Exit') exitProgram(); + return action; } // ============================================================================ @@ -62,45 +52,31 @@ export function startupQuestions(): Promise { /** * Prompt user to choose an organization. */ -export function chooseOrganization( +export async function chooseOrganization( managementAPIClient: ManagementClient, action?: string, ): Promise { - return new Promise(async (resolve, reject) => { - try { - let organizations: OrgMap; - - if (action === messages.ACTION_EXPORT_USERS || action === messages.ACTION_EXPORT_TEAMS || action === 'teams') { - organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient); - } else { - organizations = await getOrganizations(managementAPIClient); - } - - const orgList = Object.keys(organizations); - orgList.push(messages.ACTION_CANCEL); - - const _chooseOrganization = [ - { - type: 'list', - name: 'chosenOrg', - message: 'Choose an Organization', - choices: orgList, - loop: false, - }, - ]; - - inquirer - .prompt(_chooseOrganization) - .then((answers: Answers) => { - const chosenOrg = answers.chosenOrg as string; - if (chosenOrg === messages.ACTION_CANCEL) exitProgram(); - resolve({ name: chosenOrg, uid: organizations[chosenOrg] }); - }) - .catch(reject); - } catch (error) { - reject(error); - } + let organizations: OrgMap; + + if (action === messages.ACTION_EXPORT_USERS || action === messages.ACTION_EXPORT_TEAMS || action === 'teams') { + organizations = await getOrganizationsWhereUserIsAdmin(managementAPIClient); + } else { + organizations = await getOrganizations(managementAPIClient); + } + + const choices = [ + ...Object.keys(organizations).map((name) => ({ value: name })), + { value: messages.ACTION_CANCEL }, + ]; + + const chosenOrg = await select({ + message: 'Choose an Organization', + choices, + loop: false, }); + + if (chosenOrg === messages.ACTION_CANCEL) exitProgram(); + return { name: chosenOrg, uid: organizations[chosenOrg] }; } // ============================================================================ @@ -110,50 +86,35 @@ export function chooseOrganization( /** * Prompt user to choose a stack. */ -export function chooseStack( +export async function chooseStack( managementAPIClient: ManagementClient, orgUid: string, stackApiKey?: string, ): Promise { - return new Promise(async (resolve, reject) => { - try { - const stacks = await getStacks(managementAPIClient, orgUid); - - if (stackApiKey) { - const stackName = Object.keys(stacks).find((key) => stacks[key] === stackApiKey); - - if (stackName) { - resolve({ name: stackName, apiKey: stackApiKey }); - } else { - throw new Error('Could not find stack'); - } - return; - } - - const stackList = Object.keys(stacks); - stackList.push(messages.ACTION_CANCEL); - - const _chooseStack = [ - { - type: 'list', - name: 'chosenStack', - message: 'Choose a Stack', - choices: stackList, - }, - ]; - - inquirer - .prompt(_chooseStack) - .then((answers: Answers) => { - const chosenStack = answers.chosenStack as string; - if (chosenStack === messages.ACTION_CANCEL) exitProgram(); - resolve({ name: chosenStack, apiKey: stacks[chosenStack] }); - }) - .catch(reject); - } catch (error) { - reject(error); + const stacks = await getStacks(managementAPIClient, orgUid); + + if (stackApiKey) { + const stackName = Object.keys(stacks).find((key) => stacks[key] === stackApiKey); + + if (stackName) { + return { name: stackName, apiKey: stackApiKey }; + } else { + throw new Error('Could not find stack'); } + } + + const choices = [ + ...Object.keys(stacks).map((name) => ({ value: name })), + { value: messages.ACTION_CANCEL }, + ]; + + const chosenStack = await select({ + message: 'Choose a Stack', + choices, }); + + if (chosenStack === messages.ACTION_CANCEL) exitProgram(); + return { name: chosenStack, apiKey: stacks[chosenStack] }; } // ============================================================================ @@ -165,19 +126,12 @@ export function chooseStack( */ export async function chooseBranch(branchList: Branch[]): Promise { try { - const branchesArray = branchList.map((branch) => branch.uid); - - const _chooseBranch = [ - { - type: 'list', - name: 'branch', - message: 'Choose a Branch', - choices: branchesArray, - }, - ]; - - const answers = await inquirer.prompt(_chooseBranch); - return { branch: answers.branch as string }; + const branch = await select({ + message: 'Choose a Branch', + choices: branchList.map((b) => ({ value: b.uid })), + }); + + return { branch }; } catch (err) { cliux.error(err as string); throw err; @@ -191,85 +145,44 @@ export async function chooseBranch(branchList: Branch[]): Promise /** * Prompt user to choose content types (basic checkbox). */ -export function chooseContentType(stackAPIClient: StackClient, skip: number): Promise { - return new Promise(async (resolve, reject) => { - const { getContentTypes } = await import('./api-client'); - const contentTypes = await getContentTypes(stackAPIClient, skip); - const contentTypesList = Object.values(contentTypes); - - const _chooseContentType = [ - { - type: 'checkbox', - message: 'Choose Content Type (Press Space to select the content types) ', - choices: contentTypesList, - name: 'chosenContentTypes', - loop: false, - }, - ]; - - inquirer - .prompt(_chooseContentType) - .then((answers: Answers) => resolve(answers.chosenContentTypes as string[])) - .catch(reject); +export async function chooseContentType(stackAPIClient: StackClient, skip: number): Promise { + const { getContentTypes } = await import('./api-client'); + const contentTypes = await getContentTypes(stackAPIClient, skip); + const contentTypesList = Object.values(contentTypes) as string[]; + + const chosenContentTypes = await checkbox({ + message: 'Choose Content Type (Press Space to select the content types)', + choices: contentTypesList.map((ct) => ({ value: ct, name: ct })), + loop: false, }); -} -/** - * Checkbox-plus source function type. - */ -type CheckboxPlusSource = ( - answersSoFar: Record, - input: string, -) => Promise; + return chosenContentTypes; +} /** * Prompt user to choose content types (searchable multi-select). + * + * Note: inquirer-checkbox-plus-prompt is incompatible with inquirer v9+ + * (registerPrompt was removed). Replaced with checkbox() from @inquirer/prompts + * which has built-in real-time filtering — users type to search, Space to select. */ -export function chooseInMemContentTypes(contentTypesList: string[]): Promise { - return new Promise((resolve, reject) => { - const source: CheckboxPlusSource = (_answersSoFar, input) => { - input = input || ''; - const inputArray = input.split(' '); - - return new Promise((resolveSource) => { - const contentTypes = contentTypesList.filter((contentType) => { - let shouldInclude = true; - inputArray.forEach((inputChunk) => { - // if any term to filter by doesn't exist, exclude - if (!contentType.toLowerCase().includes(inputChunk.toLowerCase())) { - shouldInclude = false; - } - }); - return shouldInclude; - }); - resolveSource(contentTypes); - }); - }; - - const _chooseContentType = [ - { - type: 'checkbox-plus', - message: 'Choose Content Type (Press Space to select the content types)', - choices: contentTypesList, - name: 'chosenContentTypes', - loop: false, - highlight: true, - searchable: true, - source, - }, - ]; - - inquirer - .prompt(_chooseContentType as Parameters[0]) - .then((answers: Answers) => { - const chosenContentTypes = answers.chosenContentTypes as string[]; - if (chosenContentTypes.length === 0) { - reject('Please select atleast one content type.'); - } - resolve(chosenContentTypes); - }) - .catch(reject); - }); +export async function chooseInMemContentTypes(contentTypesList: string[]): Promise { + let chosenContentTypes: string[] = []; + + while (chosenContentTypes.length === 0) { + chosenContentTypes = await checkbox({ + message: 'Choose Content Type (Type to filter, Space to select)', + choices: contentTypesList.map((ct) => ({ value: ct, name: ct })), + loop: false, + }); + + // if any term to filter by doesn't exist, exclude + if (chosenContentTypes.length === 0) { + cliux.print('Please select atleast one content type.', { color: 'yellow' }); + } + } + + return chosenContentTypes; } // ============================================================================ @@ -279,30 +192,21 @@ export function chooseInMemContentTypes(contentTypesList: string[]): Promise { - return new Promise(async (resolve, reject) => { - const languages: LanguageMap = await getLanguages(stackAPIClient); - const languagesList = Object.keys(languages); - languagesList.push(messages.ACTION_CANCEL); - - const _chooseLanguage = [ - { - type: 'list', - message: 'Choose Language', - choices: languagesList, - name: 'chosenLanguage', - }, - ]; - - inquirer - .prompt(_chooseLanguage) - .then((answers: Answers) => { - const chosenLanguage = answers.chosenLanguage as string; - if (chosenLanguage === messages.ACTION_CANCEL) exitProgram(); - resolve({ name: chosenLanguage, code: languages[chosenLanguage] }); - }) - .catch(reject); +export async function chooseLanguage(stackAPIClient: StackClient): Promise { + const languages: LanguageMap = await getLanguages(stackAPIClient); + + const choices = [ + ...Object.keys(languages).map((name) => ({ value: name })), + { value: messages.ACTION_CANCEL }, + ]; + + const chosenLanguage = await select({ + message: 'Choose Language', + choices, }); + + if (chosenLanguage === messages.ACTION_CANCEL) exitProgram(); + return { name: chosenLanguage, code: languages[chosenLanguage] }; } // ============================================================================ @@ -312,50 +216,31 @@ export function chooseLanguage(stackAPIClient: StackClient): Promise { - return new Promise(async (resolve, reject) => { - try { - const questions = [ - { - type: 'confirm', - name: 'includeFallback', - message: 'Include fallback locale data when exporting taxonomies?', - default: false, - }, - ]; - - const firstAnswers = await inquirer.prompt(questions); - const includeFallback = firstAnswers.includeFallback as boolean; - - let fallbackLocale: string | null = null; - - if (includeFallback) { - // Get available languages for fallback locale selection - const languages: LanguageMap = await getLanguages(stackAPIClient); - const languagesList = Object.keys(languages); - - const fallbackQuestion = [ - { - type: 'list', - name: 'selectedFallbackLocale', - message: 'Choose fallback locale', - choices: languagesList, - }, - ]; - - const secondAnswers = await inquirer.prompt(fallbackQuestion); - const selectedFallbackLocale = secondAnswers.selectedFallbackLocale as string; - fallbackLocale = languages[selectedFallbackLocale]; - } - - resolve({ - includeFallback, - fallbackLocale, +export async function chooseFallbackOptions(stackAPIClient: StackClient): Promise { + try { + const includeFallback = await confirm({ + message: 'Include fallback locale data when exporting taxonomies?', + default: false, + }); + + let fallbackLocale: string | null = null; + + if (includeFallback) { + // Get available languages for fallback locale selection + const languages: LanguageMap = await getLanguages(stackAPIClient); + + const selectedFallbackLocale = await select({ + message: 'Choose fallback locale', + choices: Object.keys(languages).map((name) => ({ value: name })), }); - } catch (error) { - reject(error); + + fallbackLocale = languages[selectedFallbackLocale]; } - }); + + return { includeFallback, fallbackLocale }; + } catch (error) { + throw error; + } } // ============================================================================ @@ -366,20 +251,15 @@ export function chooseFallbackOptions(stackAPIClient: StackClient): Promise { - const export_stack_role = [ - { - type: 'list', - name: 'chooseExport', + try { + const chooseExport = await select({ message: 'Access denied: Please confirm if you still want to continue exporting the data without the { Stack Name, Stack Uid, Role Name } fields.', - choices: ['yes', 'no'], + choices: [{ value: 'yes' }, { value: 'no' }], loop: false, - }, - ]; + }); - try { - const answers = await inquirer.prompt(export_stack_role); - return answers.chooseExport === 'yes'; + return chooseExport === 'yes'; } catch (error) { cliux.print(error as string, { color: 'red' }); process.exit(1); diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index c58518115..7be960fe0 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -17,7 +17,7 @@ "@types/inquirer": "^9.0.9", "@types/jest": "^26.0.24", "@types/mkdirp": "^1.0.2", - "@types/node": "^14.18.63", + "@types/node": "^18.11.9", "@types/tar": "^6.1.13", "@types/tmp": "^0.2.6", "axios": "^1.13.5", @@ -28,7 +28,7 @@ "oclif": "^4.17.46", "ts-jest": "^29.4.6", "ts-node": "^8.10.2", - "typescript": "^4.9.5" + "typescript": "^5.9.3" }, "engines": { "node": ">=14.0.0" diff --git a/packages/contentstack-seed/src/seed/interactive.ts b/packages/contentstack-seed/src/seed/interactive.ts index f6b557b60..af10584ac 100644 --- a/packages/contentstack-seed/src/seed/interactive.ts +++ b/packages/contentstack-seed/src/seed/interactive.ts @@ -1,4 +1,4 @@ -const inquirer = require('inquirer'); +import inquirer from 'inquirer'; import { Organization, Stack } from './contentstack/client'; export interface InquireStackResponse {