From f69332d9c3dbe61ec35757198f3378d3480f08de Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 7 Feb 2023 01:19:32 +0530 Subject: [PATCH] feat: automagically migrate users of bru v1 to bru v2 --- .../bruno-app/src/utils/collections/export.js | 27 +++++ .../src/utils/importers/bruno-collection.js | 3 +- .../bruno-app/src/utils/importers/common.js | 31 +++++- .../src/utils/importers/postman-collection.js | 3 +- packages/bruno-electron/src/app/watcher.js | 31 ++++-- packages/bruno-electron/src/bru/index.js | 6 +- packages/bruno-electron/src/bru/migrate.js | 99 +++++++++++++++++++ packages/bruno-lang/v2/src/bruToJson.js | 2 +- 8 files changed, 187 insertions(+), 15 deletions(-) create mode 100644 packages/bruno-electron/src/bru/migrate.js diff --git a/packages/bruno-app/src/utils/collections/export.js b/packages/bruno-app/src/utils/collections/export.js index 43cc4f96..87b02fb2 100644 --- a/packages/bruno-app/src/utils/collections/export.js +++ b/packages/bruno-app/src/utils/collections/export.js @@ -19,6 +19,31 @@ const deleteUidsInItems = (items) => { }); }; +/** + * Some of the models in the app are not consistent with the Collection Json format + * This function is used to transform the models to the Collection Json format + */ +const transformItem = (items = []) => { + each(items, (item) => { + if (['http-request', 'graphql-request'].includes(item.type)) { + item.request.query = item.request.params; + delete item.request.params; + + if(item.type === 'graphql-request') { + item.type = 'graphql'; + } + + if(item.type === 'http-request') { + item.type = 'http'; + } + } + + if (item.items && item.items.length) { + transformItem(item.items); + } + }); +}; + const deleteUidsInEnvs = (envs) => { each(envs, (env) => { delete env.uid; @@ -31,6 +56,8 @@ const exportCollection = (collection) => { delete collection.uid; deleteUidsInItems(collection.items); deleteUidsInEnvs(collection.environments); + transformItem(collection.items); + const fileName = `${collection.name}.json`; const fileBlob = new Blob([JSON.stringify(collection, null, 2)], { type: 'application/json' }); diff --git a/packages/bruno-app/src/utils/importers/bruno-collection.js b/packages/bruno-app/src/utils/importers/bruno-collection.js index 8431d28f..8baa1a9e 100644 --- a/packages/bruno-app/src/utils/importers/bruno-collection.js +++ b/packages/bruno-app/src/utils/importers/bruno-collection.js @@ -1,6 +1,6 @@ import fileDialog from 'file-dialog'; import { BrunoError } from 'utils/common/error'; -import { validateSchema, updateUidsInCollection, hydrateSeqInCollection } from './common'; +import { validateSchema, transformItemsInCollection, updateUidsInCollection, hydrateSeqInCollection } from './common'; const readFile = (files) => { return new Promise((resolve, reject) => { @@ -30,6 +30,7 @@ const importCollection = () => { .then(parseJsonCollection) .then(hydrateSeqInCollection) .then(updateUidsInCollection) + .then(transformItemsInCollection) .then(validateSchema) .then((collection) => resolve(collection)) .catch((err) => { diff --git a/packages/bruno-app/src/utils/importers/common.js b/packages/bruno-app/src/utils/importers/common.js index 7f05b56d..12eb79f4 100644 --- a/packages/bruno-app/src/utils/importers/common.js +++ b/packages/bruno-app/src/utils/importers/common.js @@ -30,6 +30,7 @@ export const updateUidsInCollection = (_collection) => { item.uid = uuid(); each(get(item, 'request.headers'), (header) => (header.uid = uuid())); + each(get(item, 'request.query'), (param) => (param.uid = uuid())); each(get(item, 'request.params'), (param) => (param.uid = uuid())); each(get(item, 'request.body.multipartForm'), (param) => (param.uid = uuid())); each(get(item, 'request.body.formUrlEncoded'), (param) => (param.uid = uuid())); @@ -48,7 +49,35 @@ export const updateUidsInCollection = (_collection) => { }); }; updateEnvUids(collection.environments); - updateEnvUids(collection.environments); + + console.log(collection); + + return collection; +}; + +// todo +// need to eventually get rid of supporting old collection app models +// 1. start with making request type a constant fetched from a single place +// 2. move references of param and replace it with query inside the app +export const transformItemsInCollection = (collection) => { + const transformItems = (items = []) => { + each(items, (item) => { + if (['http', 'graphql'].includes(item.type)) { + item.type = `${item.type}-request`; + if(item.request.query) { + item.request.params = item.request.query; + } + + delete item.request.query; + } + + if (item.items && item.items.length) { + transformItems(item.items); + } + }); + }; + + transformItems(collection.items); return collection; }; diff --git a/packages/bruno-app/src/utils/importers/postman-collection.js b/packages/bruno-app/src/utils/importers/postman-collection.js index 65e8ca89..b1906fd4 100644 --- a/packages/bruno-app/src/utils/importers/postman-collection.js +++ b/packages/bruno-app/src/utils/importers/postman-collection.js @@ -3,7 +3,7 @@ import get from 'lodash/get'; import fileDialog from 'file-dialog'; import { uuid } from 'utils/common'; import { BrunoError } from 'utils/common/error'; -import { validateSchema, hydrateSeqInCollection } from './common'; +import { validateSchema, transformItemsInCollection, hydrateSeqInCollection } from './common'; const readFile = (files) => { return new Promise((resolve, reject) => { @@ -178,6 +178,7 @@ const importCollection = () => { fileDialog({ accept: 'application/json' }) .then(readFile) .then(parsePostmanCollection) + .then(transformItemsInCollection) .then(hydrateSeqInCollection) .then(validateSchema) .then((collection) => resolve(collection)) diff --git a/packages/bruno-electron/src/app/watcher.js b/packages/bruno-electron/src/app/watcher.js index f6c2cae7..e74ff833 100644 --- a/packages/bruno-electron/src/app/watcher.js +++ b/packages/bruno-electron/src/app/watcher.js @@ -9,6 +9,13 @@ const { bruToJson, jsonToBru } = require('../bru'); + +const { + isLegacyEnvFile, + migrateLegacyEnvFile, + isLegacyBruFile, + migrateLegacyBruFile +} = require('../bru/migrate'); const { itemSchema } = require('@usebruno/schema'); const { uuid } = require('../utils/common'); const { getRequestUid } = require('../cache/requestUids'); @@ -55,7 +62,13 @@ const addEnvironmentFile = async (win, pathname, collectionUid) => { }, }; - const bruContent = fs.readFileSync(pathname, 'utf8'); + let bruContent = fs.readFileSync(pathname, 'utf8'); + + // migrate old env json to bru file + if(isLegacyEnvFile(bruContent)) { + bruContent = await migrateLegacyEnvFile(bruContent, pathname); + } + file.data = bruToEnvJson(bruContent); file.data.name = basename.substring(0, basename.length - 4); file.data.uid = getRequestUid(pathname); @@ -117,11 +130,11 @@ const add = async (win, pathname, collectionUid, collectionPath) => { console.log(`watcher add: ${pathname}`); if(isJsonEnvironmentConfig(pathname, collectionPath)) { - // migrate old env json to bru file try { const dirname = path.dirname(pathname); - const jsonStr = fs.readFileSync(pathname, 'utf8'); - const jsonData = JSON.parse(jsonStr); + const bruContent = fs.readFileSync(pathname, 'utf8'); + + const jsonData = JSON.parse(bruContent); const envDirectory = path.join(dirname, 'environments'); if (!fs.existsSync(envDirectory)) { @@ -177,8 +190,14 @@ const add = async (win, pathname, collectionUid, collectionPath) => { } try { - const bru = fs.readFileSync(pathname, 'utf8'); - file.data = bruToJson(bru); + let bruContent = fs.readFileSync(pathname, 'utf8'); + + // migrate old bru format to new bru format + if(isLegacyBruFile(bruContent)) { + bruContent = await migrateLegacyBruFile(bruContent, pathname); + } + + file.data = bruToJson(bruContent); hydrateRequestWithUuid(file.data, pathname); win.webContents.send('main:collection-tree-updated', 'addFile', file); } catch (err) { diff --git a/packages/bruno-electron/src/bru/index.js b/packages/bruno-electron/src/bru/index.js index f3b0a65f..bb28aa74 100644 --- a/packages/bruno-electron/src/bru/index.js +++ b/packages/bruno-electron/src/bru/index.js @@ -1,9 +1,5 @@ const _ = require('lodash'); const { - bruToEnvJson: bruToEnvJsonV1, - envJsonToBru: envJsonToBruV1, - bruToJson: bruToJsonV1, - bruToJsonV2, jsonToBruV2, bruToEnvJsonV2, @@ -56,7 +52,7 @@ const bruToJson = (bru) => { } else if(requestType === "graphql") { requestType = "graphql-request"; } else { - requestType = "http"; + requestType = "http-request"; } const sequence = _.get(json, "meta.seq") diff --git a/packages/bruno-electron/src/bru/migrate.js b/packages/bruno-electron/src/bru/migrate.js new file mode 100644 index 00000000..88a78f71 --- /dev/null +++ b/packages/bruno-electron/src/bru/migrate.js @@ -0,0 +1,99 @@ +const { + bruToEnvJson: bruToEnvJsonV1, + bruToJson: bruToJsonV1, + + jsonToBruV2, + envJsonToBruV2 +} = require('@usebruno/lang'); +const _ = require('lodash'); + +const { writeFile } = require('../utils/filesystem'); + +const isLegacyEnvFile = (bruContent = '') => { + bruContent = bruContent.trim(); + let regex = /^vars[\s\S]*\/vars$/; + + return regex.test(bruContent); +}; + +const migrateLegacyEnvFile = async (bruContent, pathname) => { + const envJson = bruToEnvJsonV1(bruContent); + const newBruContent = envJsonToBruV2(envJson); + + await writeFile(pathname, newBruContent); + + return newBruContent; +}; + +const isLegacyBruFile = (bruContent = '') => { + bruContent = bruContent.trim(); + let lines = bruContent.split(/\r?\n/); + let hasName = false; + let hasMethod = false; + let hasUrl = false; + + for (let line of lines) { + line = line.trim(); + if (line.startsWith("name")) { + hasName = true; + } else if (line.startsWith("method")) { + hasMethod = true; + } else if (line.startsWith("url")) { + hasUrl = true; + } + } + + return hasName && hasMethod && hasUrl; +}; + +const migrateLegacyBruFile = async (bruContent, pathname) => { + const json = bruToJsonV1(bruContent); + + let type = _.get(json, 'type'); + if (type === 'http-request') { + type = "http"; + } else if (type === 'graphql-request') { + type = "graphql"; + } else { + type = "http"; + } + + let script = {}; + let legacyScript = _.get(json, 'request.script'); + if(legacyScript && legacyScript.trim().length > 0) { + script = { + res: legacyScript + }; + } + + const bruJson = { + meta: { + name: _.get(json, 'name'), + type: type, + seq: _.get(json, 'seq'), + }, + http: { + method: _.lowerCase(_.get(json, 'request.method')), + url: _.get(json, 'request.url'), + mode: _.get(json, 'request.body.mode', 'none') + }, + query: _.get(json, 'request.params', []), + headers: _.get(json, 'request.headers', []), + body: _.get(json, 'request.body', {}), + script: script, + tests: _.get(json, 'request.tests', ''), + }; + + const newBruContent = jsonToBruV2(bruJson); + + await writeFile(pathname, newBruContent); + + return newBruContent; +} + +module.exports = { + isLegacyEnvFile, + migrateLegacyEnvFile, + isLegacyBruFile, + migrateLegacyBruFile +}; \ No newline at end of file diff --git a/packages/bruno-lang/v2/src/bruToJson.js b/packages/bruno-lang/v2/src/bruToJson.js index 2f88ca91..3cf65f1b 100644 --- a/packages/bruno-lang/v2/src/bruToJson.js +++ b/packages/bruno-lang/v2/src/bruToJson.js @@ -38,7 +38,7 @@ const grammar = ohm.grammar(`Bru { // Dictionary Blocks dictionary = st* "{" pairlist? tagend - pairlist = optionalnl* pair (~tagend nl pair)* (~tagend space)* + pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)* pair = st* key st* ":" st* value? st* key = ~tagend validkey* value = ~tagend validvalue*