diff --git a/package-lock.json b/package-lock.json index abc881a72..b04a05b96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26292,15 +26292,72 @@ "name": "@usebruno/common", "version": "0.1.0", "license": "MIT", + "dependencies": { + "@faker-js/faker": "^9.7.0" + }, "devDependencies": { "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-typescript": "^9.0.2", + "@rollup/plugin-typescript": "^12.1.2", "rollup": "3.29.5", "rollup-plugin-dts": "^5.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", - "typescript": "^4.8.4" + "typescript": "^5.8.3" + } + }, + "packages/bruno-common/node_modules/@faker-js/faker": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", + "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "packages/bruno-common/node_modules/@rollup/plugin-typescript": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz", + "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "packages/bruno-common/node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "packages/bruno-converters": { diff --git a/packages/bruno-cli/src/runner/interpolate-vars.js b/packages/bruno-cli/src/runner/interpolate-vars.js index 514ed850e..2d11350eb 100644 --- a/packages/bruno-cli/src/runner/interpolate-vars.js +++ b/packages/bruno-cli/src/runner/interpolate-vars.js @@ -32,7 +32,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc }); }); - const _interpolate = (str) => { + const _interpolate = (str, { escapeJSONStrings } = {}) => { if (!str || !str.length || typeof str !== 'string') { return str; } @@ -51,7 +51,7 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc } }; - return interpolate(str, combinedVars); + return interpolate(str, combinedVars, { escapeJSONStrings }); }; request.url = _interpolate(request.url); @@ -67,14 +67,14 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc if (typeof request.data === 'object') { try { let parsed = JSON.stringify(request.data); - parsed = _interpolate(parsed); + parsed = _interpolate(parsed, { escapeJSONStrings: true }); request.data = JSON.parse(parsed); } catch (err) {} } if (typeof request.data === 'string') { if (request?.data?.length) { - request.data = _interpolate(request.data); + request.data = _interpolate(request.data, { escapeJSONStrings: true }); } } } else if (contentType === 'application/x-www-form-urlencoded') { diff --git a/packages/bruno-common/package.json b/packages/bruno-common/package.json index df2d6f969..9641d4db7 100644 --- a/packages/bruno-common/package.json +++ b/packages/bruno-common/package.json @@ -18,17 +18,20 @@ "build": "rollup -c", "prepack": "npm run test && npm run build" }, + "dependencies": { + "@faker-js/faker": "^9.7.0" + }, "devDependencies": { "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-typescript": "^9.0.2", + "@rollup/plugin-typescript": "^12.1.2", "rollup":"3.29.5", "rollup-plugin-dts": "^5.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-terser": "^7.0.2", - "typescript": "^4.8.4" + "typescript": "^5.8.3" }, "overrides": { - "rollup":"3.29.5" + "rollup": "3.29.5" } } diff --git a/packages/bruno-common/rollup.config.js b/packages/bruno-common/rollup.config.js index 51aedecb6..51fc15a4e 100644 --- a/packages/bruno-common/rollup.config.js +++ b/packages/bruno-common/rollup.config.js @@ -31,10 +31,5 @@ module.exports = [ typescript({ tsconfig: './tsconfig.json' }), terser() ] - }, - { - input: 'dist/esm/index.d.ts', - output: [{ file: 'dist/index.d.ts', format: 'esm' }], - plugins: [dts.default()] } ]; diff --git a/packages/bruno-common/src/interpolate/index.ts b/packages/bruno-common/src/interpolate/index.ts index 4a4092d88..6d1e51a20 100644 --- a/packages/bruno-common/src/interpolate/index.ts +++ b/packages/bruno-common/src/interpolate/index.ts @@ -11,16 +11,46 @@ * Output: Hello, my name is Bruno and I am 4 years old */ -import { Set } from 'typescript'; import { flattenObject } from '../utils'; +import { mockDataFunctions } from '../utils/faker-functions'; -const interpolate = (str: string, obj: Record): string => { - if (!str || typeof str !== 'string' || !obj || typeof obj !== 'object') { +const interpolate = ( + str: string, + obj: Record, + options: { escapeJSONStrings?: boolean } = { escapeJSONStrings: false } +): string => { + if (!str || typeof str !== 'string') { + return str; + } + + const { escapeJSONStrings } = options; + + const patternRegex = /\{\{\$(\w+)\}\}/g; + str = str.replace(patternRegex, (match, keyword) => { + let replacement = mockDataFunctions[keyword as keyof typeof mockDataFunctions]?.(); + + if (replacement === undefined) return match; + replacement = String(replacement); + + if (!escapeJSONStrings) return replacement; + + // All the below chars inside of a JSON String field + // will make it invalid JSON. So we will have to escape them with `\`. + // This is not exhaustive but selective to what faker-js can output. + if (!/[\\\n\r\t\"]/.test(replacement)) return replacement; + return replacement + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\t/g, '\\t') + .replace(/\"/g, '\\"'); + }); + + if (!obj || typeof obj !== 'object') { return str; } const flattenedObj = flattenObject(obj); - return replace(str, flattenedObj); }; diff --git a/packages/bruno-common/src/utils/faker-functions.spec.ts b/packages/bruno-common/src/utils/faker-functions.spec.ts new file mode 100644 index 000000000..8d1b482da --- /dev/null +++ b/packages/bruno-common/src/utils/faker-functions.spec.ts @@ -0,0 +1,141 @@ +import { mockDataFunctions } from "./faker-functions"; + +describe("mockDataFunctions Regex Validation", () => { + test("all values should match their expected patterns", () => { + const patterns: Record = { + guid: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/, + timestamp: /^\d{13,}$/, + isoTimestamp: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + randomUUID: /^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/, + randomAlphaNumeric: /^[\w]$/, + randomBoolean: /^(true|false)$/, + randomInt: /^\d+$/, + randomColor: /^[\w\s]+$/, + randomHexColor: /^#[\da-f]{6}$/, + randomAbbreviation: /^\w{2,6}$/, + randomIP: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$|^(\d{1,3}\.){3}\d{1,3}$/, + randomIPV4: /^(\d{1,3}\.){3}\d{1,3}$/, + randomIPV6: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/, + randomMACAddress: /^([\da-f]{2}:){5}[\da-f]{2}$/, + randomPassword: /^[\w\d]{8,}$/, + randomLocale: /^[A-Z]{2}$/, + randomUserAgent: /^[\w\/\.\s\(\)\+\-;:_,]+$/, + randomProtocol: /^(http|https|ftp)s?$/, + randomSemver: /^\d+\.\d+\.\d+$/, + randomFirstName: /^[\s\S]+$/, + randomLastName: /^[\s\S]+$/, + randomFullName: /^[\s\S]+$/, + randomNamePrefix: /^[\s\S]+$/, + randomNameSuffix: /^[\s\S]+$/, + randomJobArea: /^[\s\S]+$/, + randomJobDescriptor: /^[\s\S]+$/, + randomJobTitle: /^[\s\S]+$/, + randomJobType: /^[\s\S]+$/, + randomPhoneNumber: /^[\s\S]+$/, + randomPhoneNumberExt: /^[\s\S]+$/, + randomCity: /^[\s\S]+$/, + randomStreetName: /^[\s\S]+$/, + randomStreetAddress: /^[\s\S]+$/, + randomCountry: /^[\s\S]+$/, + randomCountryCode: /^[\s\S]+$/, + randomLatitude: /^[\s\S]+$/, + randomLongitude: /^[\s\S]+$/, + randomAvatarImage: /^[\s\S]+$/, + randomImageUrl: /^[\s\S]+$/, + randomAbstractImage: /^[\s\S]+$/, + randomAnimalsImage: /^[\s\S]+$/, + randomBusinessImage: /^[\s\S]+$/, + randomCatsImage: /^[\s\S]+$/, + randomCityImage: /^[\s\S]+$/, + randomFoodImage: /^[\s\S]+$/, + randomNightlifeImage: /^[\s\S]+$/, + randomFashionImage: /^[\s\S]+$/, + randomPeopleImage: /^[\s\S]+$/, + randomNatureImage: /^[\s\S]+$/, + randomSportsImage: /^[\s\S]+$/, + randomTransportImage: /^[\s\S]+$/, + randomImageDataUri: /^[\s\S]+$/, + randomBankAccount: /^[\s\S]+$/, + randomBankAccountName: /^[\s\S]+$/, + randomCreditCardMask: /^[\s\S]+$/, + randomBankAccountBic: /^[\s\S]+$/, + randomBankAccountIban: /^[\s\S]+$/, + randomTransactionType: /^[\s\S]+$/, + randomCurrencyCode: /^[\s\S]+$/, + randomCurrencyName: /^[\s\S]+$/, + randomCurrencySymbol: /^[\s\S]+$/, + randomBitcoin: /^[\s\S]+$/, + randomCompanyName: /^[\s\S]+$/, + randomCompanySuffix: /^[\s\S]+$/, + randomBs: /^[\s\S]+$/, + randomBsAdjective: /^[\s\S]+$/, + randomBsBuzz: /^[\s\S]+$/, + randomBsNoun: /^[\s\S]+$/, + randomCatchPhrase: /^[\s\S]+$/, + randomCatchPhraseAdjective: /^[\s\S]+$/, + randomCatchPhraseDescriptor: /^[\s\S]+$/, + randomCatchPhraseNoun: /^[\s\S]+$/, + randomDatabaseColumn: /^[\s\S]+$/, + randomDatabaseType: /^[\s\S]+$/, + randomDatabaseCollation: /^[\s\S]+$/, + randomDatabaseEngine: /^[\s\S]+$/, + randomDateFuture: /^[\s\S]+$/, + randomDatePast: /^[\s\S]+$/, + randomDateRecent: /^[\s\S]+$/, + randomWeekday: /^[\s\S]+$/, + randomMonth: /^[\s\S]+$/, + randomDomainName: /^[\s\S]+$/, + randomDomainSuffix: /^[\s\S]+$/, + randomDomainWord: /^[\s\S]+$/, + randomEmail: /^[\w_.\-]+@[\w]+\.[a-z]+$/, + randomExampleEmail: /^[\w\.-]+@example\.[a-z]+$/, + randomUserName: /^[\w.\-]+$/, + randomUrl: /^https:\/\/[\w\-]+\.[a-z]+\/?$/, + randomFileName: /^[\w\_]+\.[\w\d]+$/, + randomFileType: /^[\w]+$/, + randomFileExt: /^[\w\d]+$/, + randomCommonFileName: /^[\w\_]+\.[\w\d]+$/, + randomCommonFileType: /^[\w]+$/, + randomCommonFileExt: /^[\w\d]+$/, + randomFilePath: /^[\s\S]+$/, + randomDirectoryPath: /^\/[-\w\+\/]+$/, + randomMimeType: /^[\w]+\/[\w\d\-\+\.]+$/, + randomPrice: /^\d+\.\d{2}$/, + randomProduct: /^[\s\S]+$/, + randomProductAdjective: /^[\s\S]+$/, + randomProductMaterial: /^[\s\S]+$/, + randomProductName: /^[\s\S]+$/, + randomDepartment: /^[\s\S]+$/, + randomNoun: /^[\s\S]+$/, + randomVerb: /^[\s\S]+$/, + randomIngverb: /^[\s\S]+$/, + randomAdjective: /^[\s\S]+$/, + randomWord: /^[\s\S]+$/, + randomWords: /^[\s\S]+$/, + randomPhrase: /^[\s\S]+$/, + randomLoremWord: /^[\s\S]+$/, + randomLoremWords: /^[\s\S]+$/, + randomLoremSentence: /^[\s\S]+$/, + randomLoremSentences: /^[\s\S]+$/, + randomLoremParagraph: /^[\s\S]+$/, + randomLoremParagraphs: /^[\s\S]+$/, + randomLoremText: /^[\s\S]+$/, + randomLoremSlug: /^[\s\S]+$/, + randomLoremLines: /^[\s\S]+$/, + }; + + const errors: string[] = []; + + Object.entries(mockDataFunctions).forEach(([key, func]) => { + const pattern = patterns[key]; + const value = String(func()); + if (!value.match(pattern)) { + errors.push(`Pattern mismatch for ${key}: expected ${pattern}, received ${value}`); + } + }); + + if (errors.length > 0) { + throw new Error(errors.join("\n")); + } + }); +}); diff --git a/packages/bruno-electron/src/ipc/network/faker-functions.js b/packages/bruno-common/src/utils/faker-functions.ts similarity index 97% rename from packages/bruno-electron/src/ipc/network/faker-functions.js rename to packages/bruno-common/src/utils/faker-functions.ts index c97d262a2..64d1ed87b 100644 --- a/packages/bruno-electron/src/ipc/network/faker-functions.js +++ b/packages/bruno-common/src/utils/faker-functions.ts @@ -1,6 +1,6 @@ -const { faker } = require('@faker-js/faker'); +import { faker } from '@faker-js/faker'; -const mockDataFunctions = { +export const mockDataFunctions = { guid: () => faker.string.uuid(), timestamp: () => faker.date.anytime().getTime().toString(), isoTimestamp: () => faker.date.anytime().toISOString(), @@ -9,7 +9,7 @@ const mockDataFunctions = { randomBoolean: () => faker.datatype.boolean(), randomInt: () => faker.number.int(), randomColor: () => faker.color.human(), - randomHexColor: () => faker.internet.color(), + randomHexColor: () => faker.color.rgb(), randomAbbreviation: () => faker.hacker.abbreviation(), randomIP: () => faker.internet.ip(), randomIPV4: () => faker.internet.ipv4(), @@ -121,7 +121,3 @@ const mockDataFunctions = { randomLoremSlug: () => faker.lorem.slug(), randomLoremLines: () => faker.lorem.lines() }; - -module.exports = { - mockDataFunctions -}; diff --git a/packages/bruno-common/tsconfig.json b/packages/bruno-common/tsconfig.json index 57a8bcc74..9978d57dc 100644 --- a/packages/bruno-common/tsconfig.json +++ b/packages/bruno-common/tsconfig.json @@ -6,14 +6,14 @@ "skipLibCheck": true, "jsx": "react", "module": "ESNext", - "declaration": true, - "declarationDir": "types", "sourceMap": true, "outDir": "dist", "moduleResolution": "node", - "emitDeclarationOnly": true, "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "checkJs": false }, + "include": ["src/**/*.ts", "src/**/*.js"], "exclude": ["dist", "node_modules", "tests"] } diff --git a/packages/bruno-electron/src/ipc/network/interpolate-vars.js b/packages/bruno-electron/src/ipc/network/interpolate-vars.js index e2a5534d7..78c5454ed 100644 --- a/packages/bruno-electron/src/ipc/network/interpolate-vars.js +++ b/packages/bruno-electron/src/ipc/network/interpolate-vars.js @@ -1,7 +1,6 @@ const { interpolate } = require('@usebruno/common'); const { each, forOwn, cloneDeep, find } = require('lodash'); const FormData = require('form-data'); -const { mockDataFunctions } = require('./faker-functions'); const getContentType = (headers = {}) => { let contentType = ''; @@ -14,28 +13,6 @@ const getContentType = (headers = {}) => { return contentType; }; -const interpolateMockVars = (str, { escapeJSONStrings }) => { - const patternRegex = /\{\{\$(\w+)\}\}/g; - return str.replace(patternRegex, (match, keyword) => { - let replacement = mockDataFunctions[keyword]?.(); - - if (replacement === undefined) return match; - replacement = String(replacement); - - if (!escapeJSONStrings) return replacement; - // All the below chars inside of a JSON String field - // will make it invalid JSON. So we will have to escape them with `\`. - // This is not exhaustive but selective to what faker-js can output. - if (!/[\\\n\r\t\"]/.test(replacement)) return replacement; - return replacement - .replace(/\\/g, '\\\\') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\t/g, '\\t') - .replace(/\"/g, '\\"'); - }); -}; - const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, processEnvVars = {}) => { const globalEnvironmentVariables = request?.globalEnvironmentVariables || {}; const oauth2CredentialVariables = request?.oauth2CredentialVariables || {}; @@ -78,7 +55,9 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc } }; - return interpolateMockVars(interpolate(str, combinedVars), { escapeJSONStrings }); + return interpolate(str, combinedVars, { + escapeJSONStrings + }); }; request.url = _interpolate(request.url); @@ -97,12 +76,16 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc if (contentType.includes('json') && !Buffer.isBuffer(request.data)) { if (typeof request.data === 'string') { if (request.data.length) { - request.data = _interpolate(request.data, { escapeJSONStrings: true }); + request.data = _interpolate(request.data, { + escapeJSONStrings: true + }); } } else if (typeof request.data === 'object') { try { const jsonDoc = JSON.stringify(request.data); - const parsed = _interpolate(jsonDoc, { escapeJSONStrings: true }); + const parsed = _interpolate(jsonDoc, { + escapeJSONStrings: true + }); request.data = JSON.parse(parsed); } catch (err) {} }