From 9de35d45a0ecdb3095b86d6f1c9913be1e5de3cb Mon Sep 17 00:00:00 2001 From: Chinmay Sonawane Date: Tue, 30 Jun 2026 13:01:59 +0530 Subject: [PATCH 1/4] unique extension name for payments --- src/__tests__/setup_extension.spec.ts | 56 +++++++++++++++++++++++ src/helper/extension_utils.ts | 16 ++++++- src/lib/Extension.ts | 25 ++++++++-- src/lib/api/services/extension.service.ts | 10 ++++ src/lib/api/services/url.ts | 7 +++ 5 files changed, 108 insertions(+), 6 deletions(-) diff --git a/src/__tests__/setup_extension.spec.ts b/src/__tests__/setup_extension.spec.ts index 1c4b1ae3..fffa4462 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,55 @@ 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.", + }); + + 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 paymentNameQuestion = inquirerMock.mock.calls[3][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.", + ); + + 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)}`, + ); } }; From 959da03a1c8cd1ef6113b80fffc9fb0165237324 Mon Sep 17 00:00:00 2001 From: Chinmay Sonawane Date: Tue, 30 Jun 2026 14:30:43 +0530 Subject: [PATCH 2/4] unique extension name for payments --- src/__tests__/setup_extension.spec.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/__tests__/setup_extension.spec.ts b/src/__tests__/setup_extension.spec.ts index fffa4462..0fe5e784 100644 --- a/src/__tests__/setup_extension.spec.ts +++ b/src/__tests__/setup_extension.spec.ts @@ -322,7 +322,14 @@ describe('Extension Commands', () => { expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining("Payment extension name 'payment-ext' is already in use."), ); - const paymentNameQuestion = inquirerMock.mock.calls[3][0][0]; + 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(); From e689c67fffac97420c4baabdedb82913e97fb427 Mon Sep 17 00:00:00 2001 From: Chinmay Sonawane Date: Wed, 15 Jul 2026 14:05:59 +0530 Subject: [PATCH 3/4] add test --- src/__tests__/setup_extension.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/__tests__/setup_extension.spec.ts b/src/__tests__/setup_extension.spec.ts index 0fe5e784..690ca54c 100644 --- a/src/__tests__/setup_extension.spec.ts +++ b/src/__tests__/setup_extension.spec.ts @@ -296,6 +296,11 @@ describe('Extension Commands', () => { 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); @@ -336,6 +341,7 @@ describe('Extension Commands', () => { 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'); From f0c56107794ae663d1fc2ee72987a5d38c6eb5a8 Mon Sep 17 00:00:00 2001 From: Chinmay Sonawane Date: Mon, 20 Jul 2026 19:41:03 +0530 Subject: [PATCH 4/4] 8.0.9 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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": {