diff --git a/package-lock.json b/package-lock.json index ef69bf25..96c5db87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gofynd/fdk-cli", - "version": "8.0.8", + "version": "8.0.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gofynd/fdk-cli", - "version": "8.0.8", + "version": "8.0.9", "license": "MIT", "dependencies": { "@babel/core": "^7.24.9", diff --git a/package.json b/package.json index b843f039..0ba32600 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gofynd/fdk-cli", - "version": "8.0.8", + "version": "8.0.9", "main": "index.js", "license": "MIT", "bin": { diff --git a/src/__tests__/setup_extension.spec.ts b/src/__tests__/setup_extension.spec.ts index 1c4b1ae3..690ca54c 100644 --- a/src/__tests__/setup_extension.spec.ts +++ b/src/__tests__/setup_extension.spec.ts @@ -47,6 +47,7 @@ function cleanUp() { rimraf.sync(extensionList.items[0].name); rimraf.sync('app-ext'); rimraf.sync('payment-ext'); + rimraf.sync('unique-payment-ext'); rimraf.sync('fdk.ext.config.json'); rimraf.sync('frontend/fdk.ext.config.json'); rimraf.sync(CONSTANTS.EXTENSION_CONTEXT_FILE_NAME); @@ -262,6 +263,12 @@ describe('Extension Commands', () => { }); it('should create a payment extension', async () => { + await mockAxios + .onGet(URLS.CHECK_PAYMENT_EXTENSION_NAME('payment-ext')) + .reply(200, { + is_valid: true, + }); + const inquirerMock = mockFunction(inquirer.prompt); inquirerMock .mockResolvedValueOnce({ @@ -282,6 +289,68 @@ describe('Extension Commands', () => { expect(fs.existsSync('payment-ext')).toBe(true); }); + it('should re-prompt and validate payment extension name before asking slug', async () => { + await mockAxios + .onGet(URLS.CHECK_PAYMENT_EXTENSION_NAME('payment-ext')) + .reply(200, { + is_valid: false, + error_message: "Payment extension name 'payment-ext' is already in use.", + }); + await mockAxios + .onGet(URLS.CHECK_PAYMENT_EXTENSION_NAME('unique-payment-ext')) + .reply(200, { + is_valid: true, + }); + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined); + const inquirerMock = mockFunction(inquirer.prompt); + inquirerMock + .mockResolvedValueOnce({ + action: CONSTANTS.INIT_ACTIONS.create_extension, + }) + .mockResolvedValueOnce({ name: 'payment-ext' }) + .mockResolvedValueOnce({ type: 'Private', launch_type: 'Payment' }) + .mockResolvedValueOnce({ name: 'unique-payment-ext' }) + .mockResolvedValueOnce({ payment_mode_slug: 'unique-payment-ext' }) + .mockResolvedValueOnce({ + project_type: 'Node + React.js + SQLite(Payment)', + }); + + try { + await program.parseAsync([ + 'ts-node', + './src/fdk.ts', + 'extension', + 'init', + ]); + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("Payment extension name 'payment-ext' is already in use."), + ); + const paymentNameQuestionCall = inquirerMock.mock.calls.find(([questions]) => ( + Array.isArray(questions) + && questions[0]?.name === 'name' + && questions[0]?.message === 'Enter a different Extension name :' + )); + expect(paymentNameQuestionCall).toBeDefined(); + + const paymentNameQuestion = paymentNameQuestionCall[0][0]; + expect(paymentNameQuestion.name).toBe('name'); + expect(paymentNameQuestion.message).toBe('Enter a different Extension name :'); + expect(paymentNameQuestion.default).toBeUndefined(); + expect(await paymentNameQuestion.validate('payment-ext')).toBe( + "Payment extension name 'payment-ext' is already in use.", + ); + expect(await paymentNameQuestion.validate('unique-payment-ext')).toBe(true); + + const registerPayload = JSON.parse(mockAxios.history.post[0].data); + expect(registerPayload.name).toBe('unique-payment-ext'); + expect(fs.existsSync('unique-payment-ext')).toBe(true); + } finally { + consoleSpy.mockRestore(); + } + }); + it('should select an existing extension', async () => { const inquirerMock = mockFunction(inquirer.prompt); inquirerMock diff --git a/src/helper/extension_utils.ts b/src/helper/extension_utils.ts index 6079bcd0..20e1a148 100644 --- a/src/helper/extension_utils.ts +++ b/src/helper/extension_utils.ts @@ -317,4 +317,18 @@ export const checkAndValidatePaymentSlug = async(slug: string) => { } return true; -} \ No newline at end of file +} + +export const checkAndValidatePaymentExtensionName = async(name: string) => { + const validInput = validateEmpty(name); + if(!validInput){ + return 'Extension name is required'; + } + + const isNameAvailable = await ExtensionService.checkPaymentExtensionName(String(name).trim()); + if(!isNameAvailable.is_valid){ + return isNameAvailable.error_message || 'Payment extension name already exists. Enter a different name.'; + } + + return true; +} diff --git a/src/lib/Extension.ts b/src/lib/Extension.ts index b80588f7..b8f95dda 100644 --- a/src/lib/Extension.ts +++ b/src/lib/Extension.ts @@ -16,7 +16,8 @@ import { validateEmpty, replaceContent, selectExtensionFromList, - checkAndValidatePaymentSlug + checkAndValidatePaymentSlug, + checkAndValidatePaymentExtensionName } from '../helper/extension_utils'; import { createDirectory, writeFile, readFile } from '../helper/file.utils'; @@ -123,10 +124,6 @@ export default class Extension { answers.name = String(value.name).trim(); }); } - answers.targetDir = options['targetDir'] || answers.name; - - Extension.checkFolderAndGitExists(answers.targetDir, isExtensionNameFixed); - const extensionTypeQuestions = []; // If user wants to create new extension then ask type else it is already set in above section @@ -159,6 +156,20 @@ export default class Extension { // If launch type is Payment, ask for payment mode slug right after launch type selection // Only ask for new extensions, not when selecting existing extensions if (prompt_answers.launch_type === LAUNCH_TYPES.PAYMENT && action === INIT_ACTIONS.create_extension) { + const paymentExtensionNameValidation = await checkAndValidatePaymentExtensionName(answers.name); + if (paymentExtensionNameValidation !== true) { + console.log(`${chalk.red('>>')} ${paymentExtensionNameValidation}`); + const paymentExtensionNameAnswer = await inquirer.prompt([ + { + type: 'input', + name: 'name', + message: 'Enter a different Extension name :', + validate: checkAndValidatePaymentExtensionName, + }, + ]); + answers.name = String(paymentExtensionNameAnswer.name).trim(); + } + const paymentModeAnswer = await inquirer.prompt([ { type: 'input', @@ -170,6 +181,10 @@ export default class Extension { prompt_answers = { ...prompt_answers, ...paymentModeAnswer }; } + answers.targetDir = options['targetDir'] || answers.name; + + Extension.checkFolderAndGitExists(answers.targetDir, isExtensionNameFixed); + if(template){ const selectedLaunchType = prompt_answers.launch_type; if(TEMPLATES[template].launchTypes.includes(selectedLaunchType)){ diff --git a/src/lib/api/services/extension.service.ts b/src/lib/api/services/extension.service.ts index 1548625d..11f6e250 100644 --- a/src/lib/api/services/extension.service.ts +++ b/src/lib/api/services/extension.service.ts @@ -285,5 +285,15 @@ export default { } catch (error) { throw error; } + }, + + checkPaymentExtensionName: async (name: string) => { + try { + const axiosOption = Object.assign({}, getCommonHeaderOptions()); + const res = await ApiClient.get(URLS.CHECK_PAYMENT_EXTENSION_NAME(name), axiosOption); + return res.data; + } catch (error) { + throw error; + } } }; diff --git a/src/lib/api/services/url.ts b/src/lib/api/services/url.ts index 17e87fb3..be88bc84 100644 --- a/src/lib/api/services/url.ts +++ b/src/lib/api/services/url.ts @@ -295,5 +295,12 @@ export const URLS = { PAYMENT_URL(), `organization/${getOrganizationId()}/payment/slug?slug=${slug}`, ); + }, + + CHECK_PAYMENT_EXTENSION_NAME: (name: string) => { + return urlJoin( + PAYMENT_URL(), + `organization/${getOrganizationId()}/payment/slug?name=${encodeURIComponent(name)}`, + ); } };